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