deep-heap-profile.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
16f56ab789cb470620554d624c37f488285b3b04eDan Albert// Copyright (c) 2012 The Chromium Authors. All rights reserved. 26f56ab789cb470620554d624c37f488285b3b04eDan Albert// Use of this source code is governed by a BSD-style license that can be 36f56ab789cb470620554d624c37f488285b3b04eDan Albert// found in the LICENSE file. 46f56ab789cb470620554d624c37f488285b3b04eDan Albert 56f56ab789cb470620554d624c37f488285b3b04eDan Albert// --- 66f56ab789cb470620554d624c37f488285b3b04eDan Albert// Author: Sainbayar Sukhbaatar 76f56ab789cb470620554d624c37f488285b3b04eDan Albert// Dai Mikurube 86f56ab789cb470620554d624c37f488285b3b04eDan Albert// 96f56ab789cb470620554d624c37f488285b3b04eDan Albert 106f56ab789cb470620554d624c37f488285b3b04eDan Albert#include "deep-heap-profile.h" 116f56ab789cb470620554d624c37f488285b3b04eDan Albert 126f56ab789cb470620554d624c37f488285b3b04eDan Albert#ifdef USE_DEEP_HEAP_PROFILE 136f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <algorithm> 146f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <fcntl.h> 156f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <sys/stat.h> 166f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <sys/types.h> 176f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <time.h> 186f56ab789cb470620554d624c37f488285b3b04eDan Albert#ifdef HAVE_UNISTD_H 196f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <unistd.h> // for getpagesize and getpid 206f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif // HAVE_UNISTD_H 216f56ab789cb470620554d624c37f488285b3b04eDan Albert 226f56ab789cb470620554d624c37f488285b3b04eDan Albert#if defined(__linux__) 236f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <endian.h> 246f56ab789cb470620554d624c37f488285b3b04eDan Albert#if !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__) 256f56ab789cb470620554d624c37f488285b3b04eDan Albert#if __BYTE_ORDER == __BIG_ENDIAN 266f56ab789cb470620554d624c37f488285b3b04eDan Albert#define __BIG_ENDIAN__ 276f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif // __BYTE_ORDER == __BIG_ENDIAN 286f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif // !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__) 296f56ab789cb470620554d624c37f488285b3b04eDan Albert#if defined(__BIG_ENDIAN__) 306f56ab789cb470620554d624c37f488285b3b04eDan Albert#include <byteswap.h> 316f56ab789cb470620554d624c37f488285b3b04eDan Albert#endif // defined(__BIG_ENDIAN__) 32#endif // defined(__linux__) 33 34#include "base/cycleclock.h" 35#include "base/sysinfo.h" 36#include "internal_logging.h" // for ASSERT, etc 37 38static const int kProfilerBufferSize = 1 << 20; 39static const int kHashTableSize = 179999; // Same as heap-profile-table.cc. 40 41static const int PAGEMAP_BYTES = 8; 42static const int KPAGECOUNT_BYTES = 8; 43static const uint64 MAX_ADDRESS = kuint64max; 44 45// Tag strings in heap profile dumps. 46static const char kProfileHeader[] = "heap profile: "; 47static const char kProfileVersion[] = "DUMP_DEEP_6"; 48static const char kMetaInformationHeader[] = "META:\n"; 49static const char kMMapListHeader[] = "MMAP_LIST:\n"; 50static const char kGlobalStatsHeader[] = "GLOBAL_STATS:\n"; 51static const char kStacktraceHeader[] = "STACKTRACES:\n"; 52static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n"; 53 54static const char kVirtualLabel[] = "virtual"; 55static const char kCommittedLabel[] = "committed"; 56 57#if defined(__linux__) 58 59bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) { 60 RawFD fd; 61 char filename[100]; 62 char cmdline[4096]; 63 snprintf(filename, sizeof(filename), "/proc/%d/cmdline", 64 static_cast<int>(getpid())); 65 fd = open(filename, O_RDONLY); 66 if (fd == kIllegalRawFD) { 67 RAW_LOG(0, "Failed to open /proc/self/cmdline"); 68 return false; 69 } 70 71 size_t length = read(fd, cmdline, sizeof(cmdline) - 1); 72 close(fd); 73 74 for (int i = 0; i < length; ++i) 75 if (cmdline[i] == '\0') 76 cmdline[i] = ' '; 77 cmdline[length] = '\0'; 78 79 buffer->AppendString("CommandLine: ", 0); 80 buffer->AppendString(cmdline, 0); 81 buffer->AppendChar('\n'); 82 83 return true; 84} 85 86#else // defined(__linux__) 87 88bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) { 89 return false; 90} 91 92#endif // defined(__linux__) 93 94#if defined(__linux__) 95 96void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() { 97 char filename[100]; 98 snprintf(filename, sizeof(filename), "/proc/%d/pagemap", 99 static_cast<int>(getpid())); 100 pagemap_fd_ = open(filename, O_RDONLY); 101 RAW_CHECK(pagemap_fd_ != -1, "Failed to open /proc/self/pagemap"); 102 103 if (pageframe_type_ == DUMP_PAGECOUNT) { 104 snprintf(filename, sizeof(filename), "/proc/kpagecount", 105 static_cast<int>(getpid())); 106 kpagecount_fd_ = open(filename, O_RDONLY); 107 if (kpagecount_fd_ == -1) 108 RAW_LOG(0, "Failed to open /proc/kpagecount"); 109 } 110} 111 112size_t DeepHeapProfile::MemoryInfoGetterLinux::CommittedSize( 113 uint64 first_address, 114 uint64 last_address, 115 DeepHeapProfile::TextBuffer* buffer) const { 116 int page_size = getpagesize(); 117 uint64 page_address = (first_address / page_size) * page_size; 118 size_t committed_size = 0; 119 size_t pageframe_list_length = 0; 120 121 Seek(first_address); 122 123 // Check every page on which the allocation resides. 124 while (page_address <= last_address) { 125 // Read corresponding physical page. 126 State state; 127 // TODO(dmikurube): Read pagemap in bulk for speed. 128 // TODO(dmikurube): Consider using mincore(2). 129 if (Read(&state, pageframe_type_ != DUMP_NO_PAGEFRAME) == false) { 130 // We can't read the last region (e.g vsyscall). 131#ifndef NDEBUG 132 RAW_LOG(0, "pagemap read failed @ %#llx %"PRId64" bytes", 133 first_address, last_address - first_address + 1); 134#endif 135 return 0; 136 } 137 138 // Dump pageframes of resident pages. Non-resident pages are just skipped. 139 if (pageframe_type_ != DUMP_NO_PAGEFRAME && 140 buffer != NULL && state.pfn != 0) { 141 if (pageframe_list_length == 0) { 142 buffer->AppendString(" PF:", 0); 143 pageframe_list_length = 5; 144 } 145 buffer->AppendChar(' '); 146 if (page_address < first_address) 147 buffer->AppendChar('<'); 148 buffer->AppendBase64(state.pfn, 4); 149 pageframe_list_length += 5; 150 if (pageframe_type_ == DUMP_PAGECOUNT && IsPageCountAvailable()) { 151 uint64 pagecount = ReadPageCount(state.pfn); 152 // Assume pagecount == 63 if the pageframe is mapped more than 63 times. 153 if (pagecount > 63) 154 pagecount = 63; 155 buffer->AppendChar('#'); 156 buffer->AppendBase64(pagecount, 1); 157 pageframe_list_length += 2; 158 } 159 if (last_address < page_address - 1 + page_size) 160 buffer->AppendChar('>'); 161 // Begins a new line every 94 characters. 162 if (pageframe_list_length > 94) { 163 buffer->AppendChar('\n'); 164 pageframe_list_length = 0; 165 } 166 } 167 168 if (state.is_committed) { 169 // Calculate the size of the allocation part in this page. 170 size_t bytes = page_size; 171 172 // If looking at the last page in a given region. 173 if (last_address <= page_address - 1 + page_size) { 174 bytes = last_address - page_address + 1; 175 } 176 177 // If looking at the first page in a given region. 178 if (page_address < first_address) { 179 bytes -= first_address - page_address; 180 } 181 182 committed_size += bytes; 183 } 184 if (page_address > MAX_ADDRESS - page_size) { 185 break; 186 } 187 page_address += page_size; 188 } 189 190 if (pageframe_type_ != DUMP_NO_PAGEFRAME && 191 buffer != NULL && pageframe_list_length != 0) { 192 buffer->AppendChar('\n'); 193 } 194 195 return committed_size; 196} 197 198uint64 DeepHeapProfile::MemoryInfoGetterLinux::ReadPageCount(uint64 pfn) const { 199 int64 index = pfn * KPAGECOUNT_BYTES; 200 int64 offset = lseek64(kpagecount_fd_, index, SEEK_SET); 201 RAW_DCHECK(offset == index, "Failed in seeking in kpagecount."); 202 203 uint64 kpagecount_value; 204 int result = read(kpagecount_fd_, &kpagecount_value, KPAGECOUNT_BYTES); 205 if (result != KPAGECOUNT_BYTES) 206 return 0; 207 208 return kpagecount_value; 209} 210 211bool DeepHeapProfile::MemoryInfoGetterLinux::Seek(uint64 address) const { 212 int64 index = (address / getpagesize()) * PAGEMAP_BYTES; 213 RAW_DCHECK(pagemap_fd_ != -1, "Failed to seek in /proc/self/pagemap"); 214 int64 offset = lseek64(pagemap_fd_, index, SEEK_SET); 215 RAW_DCHECK(offset == index, "Failed in seeking."); 216 return offset >= 0; 217} 218 219bool DeepHeapProfile::MemoryInfoGetterLinux::Read( 220 State* state, bool get_pfn) const { 221 static const uint64 U64_1 = 1; 222 static const uint64 PFN_FILTER = (U64_1 << 55) - U64_1; 223 static const uint64 PAGE_PRESENT = U64_1 << 63; 224 static const uint64 PAGE_SWAP = U64_1 << 62; 225 static const uint64 PAGE_RESERVED = U64_1 << 61; 226 static const uint64 FLAG_NOPAGE = U64_1 << 20; 227 static const uint64 FLAG_KSM = U64_1 << 21; 228 static const uint64 FLAG_MMAP = U64_1 << 11; 229 230 uint64 pagemap_value; 231 RAW_DCHECK(pagemap_fd_ != -1, "Failed to read from /proc/self/pagemap"); 232 int result = read(pagemap_fd_, &pagemap_value, PAGEMAP_BYTES); 233 if (result != PAGEMAP_BYTES) { 234 return false; 235 } 236 237 // Check if the page is committed. 238 state->is_committed = (pagemap_value & (PAGE_PRESENT | PAGE_SWAP)); 239 240 state->is_present = (pagemap_value & PAGE_PRESENT); 241 state->is_swapped = (pagemap_value & PAGE_SWAP); 242 state->is_shared = false; 243 244 if (get_pfn && state->is_present && !state->is_swapped) 245 state->pfn = (pagemap_value & PFN_FILTER); 246 else 247 state->pfn = 0; 248 249 return true; 250} 251 252bool DeepHeapProfile::MemoryInfoGetterLinux::IsPageCountAvailable() const { 253 return kpagecount_fd_ != -1; 254} 255 256#endif // defined(__linux__) 257 258DeepHeapProfile::MemoryResidenceInfoGetterInterface:: 259 MemoryResidenceInfoGetterInterface() {} 260 261DeepHeapProfile::MemoryResidenceInfoGetterInterface:: 262 ~MemoryResidenceInfoGetterInterface() {} 263 264DeepHeapProfile::MemoryResidenceInfoGetterInterface* 265 DeepHeapProfile::MemoryResidenceInfoGetterInterface::Create( 266 PageFrameType pageframe_type) { 267#if defined(__linux__) 268 return new MemoryInfoGetterLinux(pageframe_type); 269#else 270 return NULL; 271#endif 272} 273 274DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile, 275 const char* prefix, 276 enum PageFrameType pageframe_type) 277 : memory_residence_info_getter_( 278 MemoryResidenceInfoGetterInterface::Create(pageframe_type)), 279 most_recent_pid_(-1), 280 stats_(), 281 dump_count_(0), 282 filename_prefix_(NULL), 283 deep_table_(kHashTableSize, heap_profile->alloc_, heap_profile->dealloc_), 284 pageframe_type_(pageframe_type), 285 heap_profile_(heap_profile) { 286 // Copy filename prefix. 287 const int prefix_length = strlen(prefix); 288 filename_prefix_ = 289 reinterpret_cast<char*>(heap_profile_->alloc_(prefix_length + 1)); 290 memcpy(filename_prefix_, prefix, prefix_length); 291 filename_prefix_[prefix_length] = '\0'; 292} 293 294DeepHeapProfile::~DeepHeapProfile() { 295 heap_profile_->dealloc_(filename_prefix_); 296 delete memory_residence_info_getter_; 297} 298 299// Global malloc() should not be used in this function. 300// Use LowLevelAlloc if required. 301void DeepHeapProfile::DumpOrderedProfile(const char* reason, 302 char raw_buffer[], 303 int buffer_size, 304 RawFD fd) { 305 TextBuffer buffer(raw_buffer, buffer_size, fd); 306 307#ifndef NDEBUG 308 int64 starting_cycles = CycleClock::Now(); 309#endif 310 311 // Get the time before starting snapshot. 312 // TODO(dmikurube): Consider gettimeofday if available. 313 time_t time_value = time(NULL); 314 315 ++dump_count_; 316 317 // Re-open files in /proc/pid/ if the process is newly forked one. 318 if (most_recent_pid_ != getpid()) { 319 most_recent_pid_ = getpid(); 320 321 memory_residence_info_getter_->Initialize(); 322 deep_table_.ResetIsLogged(); 323 324 // Write maps into "|filename_prefix_|.<pid>.maps". 325 WriteProcMaps(filename_prefix_, raw_buffer, buffer_size); 326 } 327 328 // Reset committed sizes of buckets. 329 deep_table_.ResetCommittedSize(); 330 331 // Record committed sizes. 332 stats_.SnapshotAllocations(this); 333 334 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. 335 // glibc's snprintf internally allocates memory by alloca normally, but it 336 // allocates memory by malloc if large memory is required. 337 338 buffer.AppendString(kProfileHeader, 0); 339 buffer.AppendString(kProfileVersion, 0); 340 buffer.AppendString("\n", 0); 341 342 // Fill buffer with meta information. 343 buffer.AppendString(kMetaInformationHeader, 0); 344 345 buffer.AppendString("Time: ", 0); 346 buffer.AppendUnsignedLong(time_value, 0); 347 buffer.AppendChar('\n'); 348 349 if (reason != NULL) { 350 buffer.AppendString("Reason: ", 0); 351 buffer.AppendString(reason, 0); 352 buffer.AppendChar('\n'); 353 } 354 355 AppendCommandLine(&buffer); 356 357 buffer.AppendString("PageSize: ", 0); 358 buffer.AppendInt(getpagesize(), 0, 0); 359 buffer.AppendChar('\n'); 360 361 // Assumes the physical memory <= 64GB (PFN < 2^24). 362 if (pageframe_type_ == DUMP_PAGECOUNT && 363 memory_residence_info_getter_->IsPageCountAvailable()) { 364 buffer.AppendString("PageFrame: 24,Base64,PageCount", 0); 365 buffer.AppendChar('\n'); 366 } else if (pageframe_type_ != DUMP_NO_PAGEFRAME) { 367 buffer.AppendString("PageFrame: 24,Base64", 0); 368 buffer.AppendChar('\n'); 369 } 370 371 // Fill buffer with the global stats. 372 buffer.AppendString(kMMapListHeader, 0); 373 374 stats_.SnapshotMaps(memory_residence_info_getter_, this, &buffer); 375 376 // Fill buffer with the global stats. 377 buffer.AppendString(kGlobalStatsHeader, 0); 378 379 stats_.Unparse(&buffer); 380 381 buffer.AppendString(kStacktraceHeader, 0); 382 buffer.AppendString(kVirtualLabel, 10); 383 buffer.AppendChar(' '); 384 buffer.AppendString(kCommittedLabel, 10); 385 buffer.AppendString("\n", 0); 386 387 // Fill buffer. 388 deep_table_.UnparseForStats(&buffer); 389 390 buffer.Flush(); 391 392 // Write the bucket listing into a .bucket file. 393 deep_table_.WriteForBucketFile( 394 filename_prefix_, dump_count_, raw_buffer, buffer_size); 395 396#ifndef NDEBUG 397 int64 elapsed_cycles = CycleClock::Now() - starting_cycles; 398 double elapsed_seconds = elapsed_cycles / CyclesPerSecond(); 399 RAW_LOG(0, "Time spent on DeepProfiler: %.3f sec\n", elapsed_seconds); 400#endif 401} 402 403int DeepHeapProfile::TextBuffer::Size() { 404 return size_; 405} 406 407int DeepHeapProfile::TextBuffer::FilledBytes() { 408 return cursor_; 409} 410 411void DeepHeapProfile::TextBuffer::Clear() { 412 cursor_ = 0; 413} 414 415void DeepHeapProfile::TextBuffer::Flush() { 416 RawWrite(fd_, buffer_, cursor_); 417 cursor_ = 0; 418} 419 420// TODO(dmikurube): These Append* functions should not use snprintf. 421bool DeepHeapProfile::TextBuffer::AppendChar(char value) { 422 return ForwardCursor(snprintf(buffer_ + cursor_, size_ - cursor_, 423 "%c", value)); 424} 425 426bool DeepHeapProfile::TextBuffer::AppendString(const char* value, int width) { 427 char* position = buffer_ + cursor_; 428 int available = size_ - cursor_; 429 int appended; 430 if (width == 0) 431 appended = snprintf(position, available, "%s", value); 432 else 433 appended = snprintf(position, available, "%*s", 434 width, value); 435 return ForwardCursor(appended); 436} 437 438bool DeepHeapProfile::TextBuffer::AppendInt(int value, int width, 439 bool leading_zero) { 440 char* position = buffer_ + cursor_; 441 int available = size_ - cursor_; 442 int appended; 443 if (width == 0) 444 appended = snprintf(position, available, "%d", value); 445 else if (leading_zero) 446 appended = snprintf(position, available, "%0*d", width, value); 447 else 448 appended = snprintf(position, available, "%*d", width, value); 449 return ForwardCursor(appended); 450} 451 452bool DeepHeapProfile::TextBuffer::AppendLong(long value, int width) { 453 char* position = buffer_ + cursor_; 454 int available = size_ - cursor_; 455 int appended; 456 if (width == 0) 457 appended = snprintf(position, available, "%ld", value); 458 else 459 appended = snprintf(position, available, "%*ld", width, value); 460 return ForwardCursor(appended); 461} 462 463bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value, 464 int width) { 465 char* position = buffer_ + cursor_; 466 int available = size_ - cursor_; 467 int appended; 468 if (width == 0) 469 appended = snprintf(position, available, "%lu", value); 470 else 471 appended = snprintf(position, available, "%*lu", width, value); 472 return ForwardCursor(appended); 473} 474 475bool DeepHeapProfile::TextBuffer::AppendInt64(int64 value, int width) { 476 char* position = buffer_ + cursor_; 477 int available = size_ - cursor_; 478 int appended; 479 if (width == 0) 480 appended = snprintf(position, available, "%"PRId64, value); 481 else 482 appended = snprintf(position, available, "%*"PRId64, width, value); 483 return ForwardCursor(appended); 484} 485 486bool DeepHeapProfile::TextBuffer::AppendPtr(uint64 value, int width) { 487 char* position = buffer_ + cursor_; 488 int available = size_ - cursor_; 489 int appended; 490 if (width == 0) 491 appended = snprintf(position, available, "%"PRIx64, value); 492 else 493 appended = snprintf(position, available, "%0*"PRIx64, width, value); 494 return ForwardCursor(appended); 495} 496 497bool DeepHeapProfile::TextBuffer::AppendBase64(uint64 value, int width) { 498 static const char base64[65] = 499 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 500#if defined(__BIG_ENDIAN__) 501 value = bswap_64(value); 502#endif 503 for (int shift = (width - 1) * 6; shift >= 0; shift -= 6) { 504 if (!AppendChar(base64[(value >> shift) & 0x3f])) 505 return false; 506 } 507 return true; 508} 509 510bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended) { 511 if (appended < 0 || appended >= size_ - cursor_) 512 return false; 513 cursor_ += appended; 514 if (cursor_ > size_ * 4 / 5) 515 Flush(); 516 return true; 517} 518 519void DeepHeapProfile::DeepBucket::UnparseForStats(TextBuffer* buffer) { 520 buffer->AppendInt64(bucket->alloc_size - bucket->free_size, 10); 521 buffer->AppendChar(' '); 522 buffer->AppendInt64(committed_size, 10); 523 buffer->AppendChar(' '); 524 buffer->AppendInt(bucket->allocs, 6, false); 525 buffer->AppendChar(' '); 526 buffer->AppendInt(bucket->frees, 6, false); 527 buffer->AppendString(" @ ", 0); 528 buffer->AppendInt(id, 0, false); 529 buffer->AppendString("\n", 0); 530} 531 532void DeepHeapProfile::DeepBucket::UnparseForBucketFile(TextBuffer* buffer) { 533 buffer->AppendInt(id, 0, false); 534 buffer->AppendChar(' '); 535 buffer->AppendString(is_mmap ? "mmap" : "malloc", 0); 536 537#if defined(TYPE_PROFILING) 538 buffer->AppendString(" t0x", 0); 539 buffer->AppendPtr(reinterpret_cast<uintptr_t>(type), 0); 540 if (type == NULL) { 541 buffer->AppendString(" nno_typeinfo", 0); 542 } else { 543 buffer->AppendString(" n", 0); 544 buffer->AppendString(type->name(), 0); 545 } 546#endif 547 548 for (int depth = 0; depth < bucket->depth; depth++) { 549 buffer->AppendString(" 0x", 0); 550 buffer->AppendPtr(reinterpret_cast<uintptr_t>(bucket->stack[depth]), 8); 551 } 552 buffer->AppendString("\n", 0); 553} 554 555DeepHeapProfile::DeepBucketTable::DeepBucketTable( 556 int table_size, 557 HeapProfileTable::Allocator alloc, 558 HeapProfileTable::DeAllocator dealloc) 559 : table_(NULL), 560 table_size_(table_size), 561 alloc_(alloc), 562 dealloc_(dealloc), 563 bucket_id_(0) { 564 const int bytes = table_size * sizeof(DeepBucket*); 565 table_ = reinterpret_cast<DeepBucket**>(alloc(bytes)); 566 memset(table_, 0, bytes); 567} 568 569DeepHeapProfile::DeepBucketTable::~DeepBucketTable() { 570 ASSERT(table_ != NULL); 571 for (int db = 0; db < table_size_; db++) { 572 for (DeepBucket* x = table_[db]; x != 0; /**/) { 573 DeepBucket* db = x; 574 x = x->next; 575 dealloc_(db); 576 } 577 } 578 dealloc_(table_); 579} 580 581DeepHeapProfile::DeepBucket* DeepHeapProfile::DeepBucketTable::Lookup( 582 Bucket* bucket, 583#if defined(TYPE_PROFILING) 584 const std::type_info* type, 585#endif 586 bool is_mmap) { 587 // Make hash-value 588 uintptr_t h = 0; 589 590 AddToHashValue(reinterpret_cast<uintptr_t>(bucket), &h); 591 if (is_mmap) { 592 AddToHashValue(1, &h); 593 } else { 594 AddToHashValue(0, &h); 595 } 596 597#if defined(TYPE_PROFILING) 598 if (type == NULL) { 599 AddToHashValue(0, &h); 600 } else { 601 AddToHashValue(reinterpret_cast<uintptr_t>(type->name()), &h); 602 } 603#endif 604 605 FinishHashValue(&h); 606 607 // Lookup stack trace in table 608 unsigned int buck = ((unsigned int) h) % table_size_; 609 for (DeepBucket* db = table_[buck]; db != 0; db = db->next) { 610 if (db->bucket == bucket) { 611 return db; 612 } 613 } 614 615 // Create a new bucket 616 DeepBucket* db = reinterpret_cast<DeepBucket*>(alloc_(sizeof(DeepBucket))); 617 memset(db, 0, sizeof(*db)); 618 db->bucket = bucket; 619#if defined(TYPE_PROFILING) 620 db->type = type; 621#endif 622 db->committed_size = 0; 623 db->is_mmap = is_mmap; 624 db->id = (bucket_id_++); 625 db->is_logged = false; 626 db->next = table_[buck]; 627 table_[buck] = db; 628 return db; 629} 630 631// TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. 632void DeepHeapProfile::DeepBucketTable::UnparseForStats(TextBuffer* buffer) { 633 for (int i = 0; i < table_size_; i++) { 634 for (DeepBucket* deep_bucket = table_[i]; 635 deep_bucket != NULL; 636 deep_bucket = deep_bucket->next) { 637 Bucket* bucket = deep_bucket->bucket; 638 if (bucket->alloc_size - bucket->free_size == 0) { 639 continue; // Skip empty buckets. 640 } 641 deep_bucket->UnparseForStats(buffer); 642 } 643 } 644} 645 646void DeepHeapProfile::DeepBucketTable::WriteForBucketFile( 647 const char* prefix, int dump_count, char raw_buffer[], int buffer_size) { 648 char filename[100]; 649 snprintf(filename, sizeof(filename), 650 "%s.%05d.%04d.buckets", prefix, getpid(), dump_count); 651 RawFD fd = RawOpenForWriting(filename); 652 RAW_DCHECK(fd != kIllegalRawFD, ""); 653 654 TextBuffer buffer(raw_buffer, buffer_size, fd); 655 656 for (int i = 0; i < table_size_; i++) { 657 for (DeepBucket* deep_bucket = table_[i]; 658 deep_bucket != NULL; 659 deep_bucket = deep_bucket->next) { 660 Bucket* bucket = deep_bucket->bucket; 661 if (deep_bucket->is_logged) { 662 continue; // Skip the bucket if it is already logged. 663 } 664 if (!deep_bucket->is_mmap && 665 bucket->alloc_size - bucket->free_size <= 64) { 666 continue; // Skip small malloc buckets. 667 } 668 669 deep_bucket->UnparseForBucketFile(&buffer); 670 deep_bucket->is_logged = true; 671 } 672 } 673 674 buffer.Flush(); 675 RawClose(fd); 676} 677 678void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() { 679 for (int i = 0; i < table_size_; i++) { 680 for (DeepBucket* deep_bucket = table_[i]; 681 deep_bucket != NULL; 682 deep_bucket = deep_bucket->next) { 683 deep_bucket->committed_size = 0; 684 } 685 } 686} 687 688void DeepHeapProfile::DeepBucketTable::ResetIsLogged() { 689 for (int i = 0; i < table_size_; i++) { 690 for (DeepBucket* deep_bucket = table_[i]; 691 deep_bucket != NULL; 692 deep_bucket = deep_bucket->next) { 693 deep_bucket->is_logged = false; 694 } 695 } 696} 697 698// This hash function is from HeapProfileTable::GetBucket. 699// static 700void DeepHeapProfile::DeepBucketTable::AddToHashValue( 701 uintptr_t add, uintptr_t* hash_value) { 702 *hash_value += add; 703 *hash_value += *hash_value << 10; 704 *hash_value ^= *hash_value >> 6; 705} 706 707// This hash function is from HeapProfileTable::GetBucket. 708// static 709void DeepHeapProfile::DeepBucketTable::FinishHashValue(uintptr_t* hash_value) { 710 *hash_value += *hash_value << 3; 711 *hash_value ^= *hash_value >> 11; 712} 713 714void DeepHeapProfile::RegionStats::Initialize() { 715 virtual_bytes_ = 0; 716 committed_bytes_ = 0; 717} 718 719uint64 DeepHeapProfile::RegionStats::Record( 720 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, 721 uint64 first_address, 722 uint64 last_address, 723 TextBuffer* buffer) { 724 uint64 committed; 725 virtual_bytes_ += static_cast<size_t>(last_address - first_address + 1); 726 committed = memory_residence_info_getter->CommittedSize(first_address, 727 last_address, 728 buffer); 729 committed_bytes_ += committed; 730 return committed; 731} 732 733void DeepHeapProfile::RegionStats::Unparse(const char* name, 734 TextBuffer* buffer) { 735 buffer->AppendString(name, 25); 736 buffer->AppendChar(' '); 737 buffer->AppendLong(virtual_bytes_, 12); 738 buffer->AppendChar(' '); 739 buffer->AppendLong(committed_bytes_, 12); 740 buffer->AppendString("\n", 0); 741} 742 743// Snapshots all virtual memory mappging stats by merging mmap(2) records from 744// MemoryRegionMap and /proc/maps, the OS-level memory mapping information. 745// Memory regions described in /proc/maps, but which are not created by mmap, 746// are accounted as "unhooked" memory regions. 747// 748// This function assumes that every memory region created by mmap is covered 749// by VMA(s) described in /proc/maps except for http://crbug.com/189114. 750// Note that memory regions created with mmap don't align with borders of VMAs 751// in /proc/maps. In other words, a memory region by mmap can cut across many 752// VMAs. Also, of course a VMA can include many memory regions by mmap. 753// It means that the following situation happens: 754// 755// => Virtual address 756// <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 -> 757// ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->.. 758// 759// It can happen easily as permission can be changed by mprotect(2) for a part 760// of a memory region. A change in permission splits VMA(s). 761// 762// To deal with the situation, this function iterates over MemoryRegionMap and 763// /proc/maps independently. The iterator for MemoryRegionMap is initialized 764// at the top outside the loop for /proc/maps, and it goes forward inside the 765// loop while comparing their addresses. 766// 767// TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. 768void DeepHeapProfile::GlobalStats::SnapshotMaps( 769 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, 770 DeepHeapProfile* deep_profile, 771 TextBuffer* mmap_dump_buffer) { 772 MemoryRegionMap::LockHolder lock_holder; 773 ProcMapsIterator::Buffer procmaps_iter_buffer; 774 ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer); 775 uint64 vma_start_addr, vma_last_addr, offset; 776 int64 inode; 777 char* flags; 778 char* filename; 779 enum MapsRegionType type; 780 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { 781 all_[i].Initialize(); 782 unhooked_[i].Initialize(); 783 } 784 profiled_mmap_.Initialize(); 785 786 MemoryRegionMap::RegionIterator mmap_iter = 787 MemoryRegionMap::BeginRegionLocked(); 788 DeepBucket* deep_bucket = NULL; 789 if (mmap_iter != MemoryRegionMap::EndRegionLocked()) { 790 deep_bucket = GetInformationOfMemoryRegion( 791 mmap_iter, memory_residence_info_getter, deep_profile); 792 } 793 794 while (procmaps_iter.Next(&vma_start_addr, &vma_last_addr, 795 &flags, &offset, &inode, &filename)) { 796 if (mmap_dump_buffer) { 797 char buffer[1024]; 798 int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), 799 vma_start_addr, vma_last_addr, 800 flags, offset, inode, filename, 0); 801 mmap_dump_buffer->AppendString(buffer, 0); 802 } 803 804 // 'vma_last_addr' should be the last inclusive address of the region. 805 vma_last_addr -= 1; 806 if (strcmp("[vsyscall]", filename) == 0) { 807 continue; // Reading pagemap will fail in [vsyscall]. 808 } 809 810 // TODO(dmikurube): |type| will be deprecated in the dump. 811 // See http://crbug.com/245603. 812 type = ABSENT; 813 if (filename[0] == '/') { 814 if (flags[2] == 'x') 815 type = FILE_EXEC; 816 else 817 type = FILE_NONEXEC; 818 } else if (filename[0] == '\0' || filename[0] == '\n') { 819 type = ANONYMOUS; 820 } else if (strcmp(filename, "[stack]") == 0) { 821 type = STACK; 822 } else { 823 type = OTHER; 824 } 825 // TODO(dmikurube): This |all_| count should be removed in future soon. 826 // See http://crbug.com/245603. 827 uint64 vma_total = all_[type].Record( 828 memory_residence_info_getter, vma_start_addr, vma_last_addr, NULL); 829 uint64 vma_subtotal = 0; 830 831 // TODO(dmikurube): Stop double-counting pagemap. 832 // It will be fixed when http://crbug.com/245603 finishes. 833 if (MemoryRegionMap::IsRecordingLocked()) { 834 uint64 cursor = vma_start_addr; 835 bool first = true; 836 837 // Iterates over MemoryRegionMap until the iterator moves out of the VMA. 838 do { 839 if (!first) { 840 cursor = mmap_iter->end_addr; 841 ++mmap_iter; 842 // Don't break here even if mmap_iter == EndRegionLocked(). 843 844 if (mmap_iter != MemoryRegionMap::EndRegionLocked()) { 845 deep_bucket = GetInformationOfMemoryRegion( 846 mmap_iter, memory_residence_info_getter, deep_profile); 847 } 848 } 849 first = false; 850 851 uint64 last_address_of_unhooked; 852 // If the next mmap entry is away from the current VMA. 853 if (mmap_iter == MemoryRegionMap::EndRegionLocked() || 854 mmap_iter->start_addr > vma_last_addr) { 855 last_address_of_unhooked = vma_last_addr; 856 } else { 857 last_address_of_unhooked = mmap_iter->start_addr - 1; 858 } 859 860 if (last_address_of_unhooked + 1 > cursor) { 861 RAW_CHECK(cursor >= vma_start_addr, 862 "Wrong calculation for unhooked"); 863 RAW_CHECK(last_address_of_unhooked <= vma_last_addr, 864 "Wrong calculation for unhooked"); 865 uint64 committed_size = unhooked_[type].Record( 866 memory_residence_info_getter, 867 cursor, 868 last_address_of_unhooked, 869 mmap_dump_buffer); 870 vma_subtotal += committed_size; 871 if (mmap_dump_buffer) { 872 mmap_dump_buffer->AppendString(" ", 0); 873 mmap_dump_buffer->AppendPtr(cursor, 0); 874 mmap_dump_buffer->AppendString(" - ", 0); 875 mmap_dump_buffer->AppendPtr(last_address_of_unhooked + 1, 0); 876 mmap_dump_buffer->AppendString(" unhooked ", 0); 877 mmap_dump_buffer->AppendInt64(committed_size, 0); 878 mmap_dump_buffer->AppendString(" / ", 0); 879 mmap_dump_buffer->AppendInt64( 880 last_address_of_unhooked - cursor + 1, 0); 881 mmap_dump_buffer->AppendString("\n", 0); 882 } 883 cursor = last_address_of_unhooked + 1; 884 } 885 886 if (mmap_iter != MemoryRegionMap::EndRegionLocked() && 887 mmap_iter->start_addr <= vma_last_addr && 888 mmap_dump_buffer) { 889 bool trailing = mmap_iter->start_addr < vma_start_addr; 890 bool continued = mmap_iter->end_addr - 1 > vma_last_addr; 891 uint64 partial_first_address, partial_last_address; 892 if (trailing) 893 partial_first_address = vma_start_addr; 894 else 895 partial_first_address = mmap_iter->start_addr; 896 if (continued) 897 partial_last_address = vma_last_addr; 898 else 899 partial_last_address = mmap_iter->end_addr - 1; 900 uint64 committed_size = memory_residence_info_getter->CommittedSize( 901 partial_first_address, partial_last_address, mmap_dump_buffer); 902 vma_subtotal += committed_size; 903 mmap_dump_buffer->AppendString(trailing ? " (" : " ", 0); 904 mmap_dump_buffer->AppendPtr(mmap_iter->start_addr, 0); 905 mmap_dump_buffer->AppendString(trailing ? ")" : " ", 0); 906 mmap_dump_buffer->AppendString("-", 0); 907 mmap_dump_buffer->AppendString(continued ? "(" : " ", 0); 908 mmap_dump_buffer->AppendPtr(mmap_iter->end_addr, 0); 909 mmap_dump_buffer->AppendString(continued ? ")" : " ", 0); 910 mmap_dump_buffer->AppendString(" hooked ", 0); 911 mmap_dump_buffer->AppendInt64(committed_size, 0); 912 mmap_dump_buffer->AppendString(" / ", 0); 913 mmap_dump_buffer->AppendInt64( 914 partial_last_address - partial_first_address + 1, 0); 915 mmap_dump_buffer->AppendString(" @ ", 0); 916 if (deep_bucket != NULL) { 917 mmap_dump_buffer->AppendInt(deep_bucket->id, 0, false); 918 } else { 919 mmap_dump_buffer->AppendInt(0, 0, false); 920 } 921 mmap_dump_buffer->AppendString("\n", 0); 922 } 923 } while (mmap_iter != MemoryRegionMap::EndRegionLocked() && 924 mmap_iter->end_addr - 1 <= vma_last_addr); 925 } 926 927 if (vma_total != vma_subtotal) { 928 char buffer[1024]; 929 int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), 930 vma_start_addr, vma_last_addr, 931 flags, offset, inode, filename, 0); 932 RAW_LOG(0, "[%d] Mismatched total in VMA %"PRId64":%"PRId64" (%"PRId64")", 933 getpid(), vma_total, vma_subtotal, vma_total - vma_subtotal); 934 RAW_LOG(0, "[%d] in %s", getpid(), buffer); 935 } 936 } 937 938 // TODO(dmikurube): Investigate and fix http://crbug.com/189114. 939 // 940 // The total committed memory usage in all_ (from /proc/<pid>/maps) is 941 // sometimes smaller than the sum of the committed mmap'ed addresses and 942 // unhooked regions. Within our observation, the difference was only 4KB 943 // in committed usage, zero in reserved virtual addresses 944 // 945 // A guess is that an uncommitted (but reserved) page may become committed 946 // during counting memory usage in the loop above. 947 // 948 // The difference is accounted as "ABSENT" to investigate such cases. 949 // 950 // It will be fixed when http://crbug.com/245603 finishes (no double count). 951 952 RegionStats all_total; 953 RegionStats unhooked_total; 954 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { 955 all_total.AddAnotherRegionStat(all_[i]); 956 unhooked_total.AddAnotherRegionStat(unhooked_[i]); 957 } 958 959 size_t absent_virtual = profiled_mmap_.virtual_bytes() + 960 unhooked_total.virtual_bytes() - 961 all_total.virtual_bytes(); 962 if (absent_virtual > 0) 963 all_[ABSENT].AddToVirtualBytes(absent_virtual); 964 965 size_t absent_committed = profiled_mmap_.committed_bytes() + 966 unhooked_total.committed_bytes() - 967 all_total.committed_bytes(); 968 if (absent_committed > 0) 969 all_[ABSENT].AddToCommittedBytes(absent_committed); 970} 971 972void DeepHeapProfile::GlobalStats::SnapshotAllocations( 973 DeepHeapProfile* deep_profile) { 974 profiled_malloc_.Initialize(); 975 976 deep_profile->heap_profile_->address_map_->Iterate(RecordAlloc, deep_profile); 977} 978 979void DeepHeapProfile::GlobalStats::Unparse(TextBuffer* buffer) { 980 RegionStats all_total; 981 RegionStats unhooked_total; 982 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { 983 all_total.AddAnotherRegionStat(all_[i]); 984 unhooked_total.AddAnotherRegionStat(unhooked_[i]); 985 } 986 987 // "# total (%lu) %c= profiled-mmap (%lu) + nonprofiled-* (%lu)\n" 988 buffer->AppendString("# total (", 0); 989 buffer->AppendUnsignedLong(all_total.committed_bytes(), 0); 990 buffer->AppendString(") ", 0); 991 buffer->AppendChar(all_total.committed_bytes() == 992 profiled_mmap_.committed_bytes() + 993 unhooked_total.committed_bytes() ? '=' : '!'); 994 buffer->AppendString("= profiled-mmap (", 0); 995 buffer->AppendUnsignedLong(profiled_mmap_.committed_bytes(), 0); 996 buffer->AppendString(") + nonprofiled-* (", 0); 997 buffer->AppendUnsignedLong(unhooked_total.committed_bytes(), 0); 998 buffer->AppendString(")\n", 0); 999 1000 // " virtual committed" 1001 buffer->AppendString("", 26); 1002 buffer->AppendString(kVirtualLabel, 12); 1003 buffer->AppendChar(' '); 1004 buffer->AppendString(kCommittedLabel, 12); 1005 buffer->AppendString("\n", 0); 1006 1007 all_total.Unparse("total", buffer); 1008 all_[ABSENT].Unparse("absent", buffer); 1009 all_[FILE_EXEC].Unparse("file-exec", buffer); 1010 all_[FILE_NONEXEC].Unparse("file-nonexec", buffer); 1011 all_[ANONYMOUS].Unparse("anonymous", buffer); 1012 all_[STACK].Unparse("stack", buffer); 1013 all_[OTHER].Unparse("other", buffer); 1014 unhooked_total.Unparse("nonprofiled-total", buffer); 1015 unhooked_[ABSENT].Unparse("nonprofiled-absent", buffer); 1016 unhooked_[ANONYMOUS].Unparse("nonprofiled-anonymous", buffer); 1017 unhooked_[FILE_EXEC].Unparse("nonprofiled-file-exec", buffer); 1018 unhooked_[FILE_NONEXEC].Unparse("nonprofiled-file-nonexec", buffer); 1019 unhooked_[STACK].Unparse("nonprofiled-stack", buffer); 1020 unhooked_[OTHER].Unparse("nonprofiled-other", buffer); 1021 profiled_mmap_.Unparse("profiled-mmap", buffer); 1022 profiled_malloc_.Unparse("profiled-malloc", buffer); 1023} 1024 1025// static 1026void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer, 1027 AllocValue* alloc_value, 1028 DeepHeapProfile* deep_profile) { 1029 uint64 address = reinterpret_cast<uintptr_t>(pointer); 1030 size_t committed = deep_profile->memory_residence_info_getter_->CommittedSize( 1031 address, address + alloc_value->bytes - 1, NULL); 1032 1033 DeepBucket* deep_bucket = deep_profile->deep_table_.Lookup( 1034 alloc_value->bucket(), 1035#if defined(TYPE_PROFILING) 1036 LookupType(pointer), 1037#endif 1038 /* is_mmap */ false); 1039 deep_bucket->committed_size += committed; 1040 deep_profile->stats_.profiled_malloc_.AddToVirtualBytes(alloc_value->bytes); 1041 deep_profile->stats_.profiled_malloc_.AddToCommittedBytes(committed); 1042} 1043 1044DeepHeapProfile::DeepBucket* 1045 DeepHeapProfile::GlobalStats::GetInformationOfMemoryRegion( 1046 const MemoryRegionMap::RegionIterator& mmap_iter, 1047 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, 1048 DeepHeapProfile* deep_profile) { 1049 size_t committed = deep_profile->memory_residence_info_getter_-> 1050 CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1, NULL); 1051 1052 // TODO(dmikurube): Store a reference to the bucket in region. 1053 Bucket* bucket = MemoryRegionMap::GetBucket( 1054 mmap_iter->call_stack_depth, mmap_iter->call_stack); 1055 DeepBucket* deep_bucket = NULL; 1056 if (bucket != NULL) { 1057 deep_bucket = deep_profile->deep_table_.Lookup( 1058 bucket, 1059#if defined(TYPE_PROFILING) 1060 NULL, // No type information for memory regions by mmap. 1061#endif 1062 /* is_mmap */ true); 1063 if (deep_bucket != NULL) 1064 deep_bucket->committed_size += committed; 1065 } 1066 1067 profiled_mmap_.AddToVirtualBytes( 1068 mmap_iter->end_addr - mmap_iter->start_addr); 1069 profiled_mmap_.AddToCommittedBytes(committed); 1070 1071 return deep_bucket; 1072} 1073 1074// static 1075void DeepHeapProfile::WriteProcMaps(const char* prefix, 1076 char raw_buffer[], 1077 int buffer_size) { 1078 char filename[100]; 1079 snprintf(filename, sizeof(filename), 1080 "%s.%05d.maps", prefix, static_cast<int>(getpid())); 1081 1082 RawFD fd = RawOpenForWriting(filename); 1083 RAW_DCHECK(fd != kIllegalRawFD, ""); 1084 1085 int length; 1086 bool wrote_all; 1087 length = tcmalloc::FillProcSelfMaps(raw_buffer, buffer_size, &wrote_all); 1088 RAW_DCHECK(wrote_all, ""); 1089 RAW_DCHECK(length <= buffer_size, ""); 1090 RawWrite(fd, raw_buffer, length); 1091 RawClose(fd); 1092} 1093#else // USE_DEEP_HEAP_PROFILE 1094 1095DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile, 1096 const char* prefix) 1097 : heap_profile_(heap_profile) { 1098} 1099 1100DeepHeapProfile::~DeepHeapProfile() { 1101} 1102 1103int DeepHeapProfile::DumpOrderedProfile(const char* reason, 1104 char raw_buffer[], 1105 int buffer_size, 1106 RawFD fd) { 1107} 1108 1109#endif // USE_DEEP_HEAP_PROFILE 1110