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