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.
6 // Author: Sainbayar Sukhbaatar
10 #include "deep-heap-profile.h"
12 #ifdef USE_DEEP_HEAP_PROFILE
16 #include <sys/types.h>
19 #include <unistd.h> // for getpagesize and getpid
20 #endif // HAVE_UNISTD_H
22 #if defined(__linux__)
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__)
31 #endif // defined(__BIG_ENDIAN__)
32 #endif // defined(__linux__)
34 #include "base/cycleclock.h"
35 #include "base/sysinfo.h"
36 #include "internal_logging.h" // for ASSERT, etc
38 static const int kProfilerBufferSize = 1 << 20;
39 static const int kHashTableSize = 179999; // Same as heap-profile-table.cc.
41 static const int PAGEMAP_BYTES = 8;
42 static const int KPAGECOUNT_BYTES = 8;
43 static const uint64 MAX_ADDRESS = kuint64max;
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";
54 static const char kVirtualLabel[] = "virtual";
55 static const char kCommittedLabel[] = "committed";
57 #if defined(__linux__)
59 bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) {
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");
71 size_t length = read(fd, cmdline, sizeof(cmdline) - 1);
74 for (int i = 0; i < length; ++i)
75 if (cmdline[i] == '\0')
77 cmdline[length] = '\0';
79 buffer->AppendString("CommandLine: ", 0);
80 buffer->AppendString(cmdline, 0);
81 buffer->AppendChar('\n');
86 #else // defined(__linux__)
88 bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) {
92 #endif // defined(__linux__)
94 #if defined(__linux__)
96 void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() {
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");
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");
112 size_t DeepHeapProfile::MemoryInfoGetterLinux::CommittedSize(
113 uint64 first_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;
123 // Check every page on which the allocation resides.
124 while (page_address <= last_address) {
125 // Read corresponding physical page.
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).
132 RAW_LOG(0, "pagemap read failed @ %#llx %" PRId64 " bytes",
133 first_address, last_address - first_address + 1);
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;
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.
155 buffer->AppendChar('#');
156 buffer->AppendBase64(pagecount, 1);
157 pageframe_list_length += 2;
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;
168 if (state.is_committed) {
169 // Calculate the size of the allocation part in this page.
170 size_t bytes = page_size;
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;
177 // If looking at the first page in a given region.
178 if (page_address < first_address) {
179 bytes -= first_address - page_address;
182 committed_size += bytes;
184 if (page_address > MAX_ADDRESS - page_size) {
187 page_address += page_size;
190 if (pageframe_type_ != DUMP_NO_PAGEFRAME &&
191 buffer != NULL && pageframe_list_length != 0) {
192 buffer->AppendChar('\n');
195 return committed_size;
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.");
203 uint64 kpagecount_value;
204 int result = read(kpagecount_fd_, &kpagecount_value, KPAGECOUNT_BYTES);
205 if (result != KPAGECOUNT_BYTES)
208 return kpagecount_value;
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.");
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;
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) {
237 // Check if the page is committed.
238 state->is_committed = (pagemap_value & (PAGE_PRESENT | PAGE_SWAP));
240 state->is_present = (pagemap_value & PAGE_PRESENT);
241 state->is_swapped = (pagemap_value & PAGE_SWAP);
242 state->is_shared = false;
244 if (get_pfn && state->is_present && !state->is_swapped)
245 state->pfn = (pagemap_value & PFN_FILTER);
252 bool DeepHeapProfile::MemoryInfoGetterLinux::IsPageCountAvailable() const {
253 return kpagecount_fd_ != -1;
256 #endif // defined(__linux__)
258 DeepHeapProfile::MemoryResidenceInfoGetterInterface::
259 MemoryResidenceInfoGetterInterface() {}
261 DeepHeapProfile::MemoryResidenceInfoGetterInterface::
262 ~MemoryResidenceInfoGetterInterface() {}
264 DeepHeapProfile::MemoryResidenceInfoGetterInterface*
265 DeepHeapProfile::MemoryResidenceInfoGetterInterface::Create(
266 PageFrameType pageframe_type) {
267 #if defined(__linux__)
268 return new MemoryInfoGetterLinux(pageframe_type);
274 DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
276 enum PageFrameType pageframe_type)
277 : memory_residence_info_getter_(
278 MemoryResidenceInfoGetterInterface::Create(pageframe_type)),
279 most_recent_pid_(-1),
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);
289 reinterpret_cast<char*>(heap_profile_->alloc_(prefix_length + 1));
290 memcpy(filename_prefix_, prefix, prefix_length);
291 filename_prefix_[prefix_length] = '\0';
293 strncpy(run_id_, "undetermined-run-id", sizeof(run_id_));
296 DeepHeapProfile::~DeepHeapProfile() {
297 heap_profile_->dealloc_(filename_prefix_);
298 delete memory_residence_info_getter_;
301 // Global malloc() should not be used in this function.
302 // Use LowLevelAlloc if required.
303 void DeepHeapProfile::DumpOrderedProfile(const char* reason,
307 TextBuffer buffer(raw_buffer, buffer_size, fd);
310 int64 starting_cycles = CycleClock::Now();
313 // Get the time before starting snapshot.
314 // TODO(dmikurube): Consider gettimeofday if available.
315 time_t time_value = time(NULL);
319 // Re-open files in /proc/pid/ if the process is newly forked one.
320 if (most_recent_pid_ != getpid()) {
322 if (0 == gethostname(hostname, sizeof(hostname))) {
323 char* dot = strchr(hostname, '.');
327 strcpy(hostname, "unknown");
330 most_recent_pid_ = getpid();
332 snprintf(run_id_, sizeof(run_id_), "%s-linux-%d-%lu",
333 hostname, most_recent_pid_, time(NULL));
335 memory_residence_info_getter_->Initialize();
336 deep_table_.ResetIsLogged();
338 // Write maps into "|filename_prefix_|.<pid>.maps".
339 WriteProcMaps(filename_prefix_, raw_buffer, buffer_size);
342 // Reset committed sizes of buckets.
343 deep_table_.ResetCommittedSize();
345 // Record committed sizes.
346 stats_.SnapshotAllocations(this);
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.
352 buffer.AppendString(kProfileHeader, 0);
353 buffer.AppendString(kProfileVersion, 0);
354 buffer.AppendString("\n", 0);
356 // Fill buffer with meta information.
357 buffer.AppendString(kMetaInformationHeader, 0);
359 buffer.AppendString("Time: ", 0);
360 buffer.AppendUnsignedLong(time_value, 0);
361 buffer.AppendChar('\n');
363 if (reason != NULL) {
364 buffer.AppendString("Reason: ", 0);
365 buffer.AppendString(reason, 0);
366 buffer.AppendChar('\n');
369 AppendCommandLine(&buffer);
371 buffer.AppendString("RunID: ", 0);
372 buffer.AppendString(run_id_, 0);
373 buffer.AppendChar('\n');
375 buffer.AppendString("PageSize: ", 0);
376 buffer.AppendInt(getpagesize(), 0, 0);
377 buffer.AppendChar('\n');
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');
389 // Fill buffer with the global stats.
390 buffer.AppendString(kMMapListHeader, 0);
392 stats_.SnapshotMaps(memory_residence_info_getter_, this, &buffer);
394 // Fill buffer with the global stats.
395 buffer.AppendString(kGlobalStatsHeader, 0);
397 stats_.Unparse(&buffer);
399 buffer.AppendString(kStacktraceHeader, 0);
400 buffer.AppendString(kVirtualLabel, 10);
401 buffer.AppendChar(' ');
402 buffer.AppendString(kCommittedLabel, 10);
403 buffer.AppendString("\n", 0);
406 deep_table_.UnparseForStats(&buffer);
410 // Write the bucket listing into a .bucket file.
411 deep_table_.WriteForBucketFile(
412 filename_prefix_, dump_count_, raw_buffer, buffer_size);
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);
421 int DeepHeapProfile::TextBuffer::Size() {
425 int DeepHeapProfile::TextBuffer::FilledBytes() {
429 void DeepHeapProfile::TextBuffer::Clear() {
433 void DeepHeapProfile::TextBuffer::Flush() {
434 RawWrite(fd_, buffer_, cursor_);
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_,
444 bool DeepHeapProfile::TextBuffer::AppendString(const char* value, int width) {
445 char* position = buffer_ + cursor_;
446 int available = size_ - cursor_;
449 appended = snprintf(position, available, "%s", value);
451 appended = snprintf(position, available, "%*s",
453 return ForwardCursor(appended);
456 bool DeepHeapProfile::TextBuffer::AppendInt(int value, int width,
458 char* position = buffer_ + cursor_;
459 int available = size_ - cursor_;
462 appended = snprintf(position, available, "%d", value);
463 else if (leading_zero)
464 appended = snprintf(position, available, "%0*d", width, value);
466 appended = snprintf(position, available, "%*d", width, value);
467 return ForwardCursor(appended);
470 bool DeepHeapProfile::TextBuffer::AppendLong(long value, int width) {
471 char* position = buffer_ + cursor_;
472 int available = size_ - cursor_;
475 appended = snprintf(position, available, "%ld", value);
477 appended = snprintf(position, available, "%*ld", width, value);
478 return ForwardCursor(appended);
481 bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value,
483 char* position = buffer_ + cursor_;
484 int available = size_ - cursor_;
487 appended = snprintf(position, available, "%lu", value);
489 appended = snprintf(position, available, "%*lu", width, value);
490 return ForwardCursor(appended);
493 bool DeepHeapProfile::TextBuffer::AppendInt64(int64 value, int width) {
494 char* position = buffer_ + cursor_;
495 int available = size_ - cursor_;
498 appended = snprintf(position, available, "%" PRId64, value);
500 appended = snprintf(position, available, "%*" PRId64, width, value);
501 return ForwardCursor(appended);
504 bool DeepHeapProfile::TextBuffer::AppendPtr(uint64 value, int width) {
505 char* position = buffer_ + cursor_;
506 int available = size_ - cursor_;
509 appended = snprintf(position, available, "%" PRIx64, value);
511 appended = snprintf(position, available, "%0*" PRIx64, width, value);
512 return ForwardCursor(appended);
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);
521 for (int shift = (width - 1) * 6; shift >= 0; shift -= 6) {
522 if (!AppendChar(base64[(value >> shift) & 0x3f]))
528 bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended) {
529 if (appended < 0 || appended >= size_ - cursor_)
532 if (cursor_ > size_ * 4 / 5)
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);
550 void DeepHeapProfile::DeepBucket::UnparseForBucketFile(TextBuffer* buffer) {
551 buffer->AppendInt(id, 0, false);
552 buffer->AppendChar(' ');
553 buffer->AppendString(is_mmap ? "mmap" : "malloc", 0);
555 #if defined(TYPE_PROFILING)
556 buffer->AppendString(" t0x", 0);
557 buffer->AppendPtr(reinterpret_cast<uintptr_t>(type), 0);
559 buffer->AppendString(" nno_typeinfo", 0);
561 buffer->AppendString(" n", 0);
562 buffer->AppendString(type->name(), 0);
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);
570 buffer->AppendString("\n", 0);
573 DeepHeapProfile::DeepBucketTable::DeepBucketTable(
575 HeapProfileTable::Allocator alloc,
576 HeapProfileTable::DeAllocator dealloc)
578 table_size_(table_size),
582 const int bytes = table_size * sizeof(DeepBucket*);
583 table_ = reinterpret_cast<DeepBucket**>(alloc(bytes));
584 memset(table_, 0, bytes);
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; /**/) {
599 DeepHeapProfile::DeepBucket* DeepHeapProfile::DeepBucketTable::Lookup(
601 #if defined(TYPE_PROFILING)
602 const std::type_info* type,
608 AddToHashValue(reinterpret_cast<uintptr_t>(bucket), &h);
610 AddToHashValue(1, &h);
612 AddToHashValue(0, &h);
615 #if defined(TYPE_PROFILING)
617 AddToHashValue(0, &h);
619 AddToHashValue(reinterpret_cast<uintptr_t>(type->name()), &h);
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) {
633 // Create a new bucket
634 DeepBucket* db = reinterpret_cast<DeepBucket*>(alloc_(sizeof(DeepBucket)));
635 memset(db, 0, sizeof(*db));
637 #if defined(TYPE_PROFILING)
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];
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];
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.
659 deep_bucket->UnparseForStats(buffer);
664 void DeepHeapProfile::DeepBucketTable::WriteForBucketFile(
665 const char* prefix, int dump_count, char raw_buffer[], int buffer_size) {
667 snprintf(filename, sizeof(filename),
668 "%s.%05d.%04d.buckets", prefix, getpid(), dump_count);
669 RawFD fd = RawOpenForWriting(filename);
670 RAW_DCHECK(fd != kIllegalRawFD, "");
672 TextBuffer buffer(raw_buffer, buffer_size, fd);
674 for (int i = 0; i < table_size_; i++) {
675 for (DeepBucket* deep_bucket = table_[i];
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.
682 if (!deep_bucket->is_mmap &&
683 bucket->alloc_size - bucket->free_size <= 64) {
684 continue; // Skip small malloc buckets.
687 deep_bucket->UnparseForBucketFile(&buffer);
688 deep_bucket->is_logged = true;
696 void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() {
697 for (int i = 0; i < table_size_; i++) {
698 for (DeepBucket* deep_bucket = table_[i];
700 deep_bucket = deep_bucket->next) {
701 deep_bucket->committed_size = 0;
706 void DeepHeapProfile::DeepBucketTable::ResetIsLogged() {
707 for (int i = 0; i < table_size_; i++) {
708 for (DeepBucket* deep_bucket = table_[i];
710 deep_bucket = deep_bucket->next) {
711 deep_bucket->is_logged = false;
716 // This hash function is from HeapProfileTable::GetBucket.
718 void DeepHeapProfile::DeepBucketTable::AddToHashValue(
719 uintptr_t add, uintptr_t* hash_value) {
721 *hash_value += *hash_value << 10;
722 *hash_value ^= *hash_value >> 6;
725 // This hash function is from HeapProfileTable::GetBucket.
727 void DeepHeapProfile::DeepBucketTable::FinishHashValue(uintptr_t* hash_value) {
728 *hash_value += *hash_value << 3;
729 *hash_value ^= *hash_value >> 11;
732 void DeepHeapProfile::RegionStats::Initialize() {
734 committed_bytes_ = 0;
737 uint64 DeepHeapProfile::RegionStats::Record(
738 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter,
739 uint64 first_address,
741 TextBuffer* buffer) {
743 virtual_bytes_ += static_cast<size_t>(last_address - first_address + 1);
744 committed = memory_residence_info_getter->CommittedSize(first_address,
747 committed_bytes_ += committed;
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);
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.
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:
773 // => Virtual address
774 // <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 ->
775 // ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->..
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).
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.
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;
797 enum MapsRegionType type;
798 for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) {
799 all_[i].Initialize();
800 unhooked_[i].Initialize();
802 profiled_mmap_.Initialize();
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);
812 while (procmaps_iter.Next(&vma_start_addr, &vma_last_addr,
813 &flags, &offset, &inode, &filename)) {
814 if (mmap_dump_buffer) {
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);
822 // 'vma_last_addr' should be the last inclusive address of the region.
824 if (strcmp("[vsyscall]", filename) == 0) {
825 continue; // Reading pagemap will fail in [vsyscall].
828 // TODO(dmikurube): |type| will be deprecated in the dump.
829 // See http://crbug.com/245603.
831 if (filename[0] == '/') {
836 } else if (filename[0] == '\0' || filename[0] == '\n') {
838 } else if (strcmp(filename, "[stack]") == 0) {
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;
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;
855 // Iterates over MemoryRegionMap until the iterator moves out of the VMA.
858 cursor = mmap_iter->end_addr;
860 // Don't break here even if mmap_iter == EndRegionLocked().
862 if (mmap_iter != MemoryRegionMap::EndRegionLocked()) {
863 deep_bucket = GetInformationOfMemoryRegion(
864 mmap_iter, memory_residence_info_getter, deep_profile);
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;
875 last_address_of_unhooked = mmap_iter->start_addr - 1;
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,
886 last_address_of_unhooked,
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);
901 cursor = last_address_of_unhooked + 1;
904 if (mmap_iter != MemoryRegionMap::EndRegionLocked() &&
905 mmap_iter->start_addr <= vma_last_addr &&
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;
911 partial_first_address = vma_start_addr;
913 partial_first_address = mmap_iter->start_addr;
915 partial_last_address = vma_last_addr;
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);
937 mmap_dump_buffer->AppendInt(0, 0, false);
939 mmap_dump_buffer->AppendString("\n", 0);
941 } while (mmap_iter != MemoryRegionMap::EndRegionLocked() &&
942 mmap_iter->end_addr - 1 <= vma_last_addr);
945 if (vma_total != vma_subtotal) {
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);
957 // TODO(dmikurube): Investigate and fix http://crbug.com/189114.
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
964 // A guess is that an uncommitted (but reserved) page may become committed
965 // during counting memory usage in the loop above.
967 // The difference is accounted as "ABSENT" to investigate such cases.
969 // It will be fixed when http://crbug.com/245603 finishes (no double count).
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]);
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);
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);
991 void DeepHeapProfile::GlobalStats::SnapshotAllocations(
992 DeepHeapProfile* deep_profile) {
993 profiled_malloc_.Initialize();
995 deep_profile->heap_profile_->address_map_->Iterate(RecordAlloc, deep_profile);
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]);
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);
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);
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);
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);
1052 DeepBucket* deep_bucket = deep_profile->deep_table_.Lookup(
1053 alloc_value->bucket(),
1054 #if defined(TYPE_PROFILING)
1055 LookupType(pointer),
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);
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);
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(
1078 #if defined(TYPE_PROFILING)
1079 NULL, // No type information for memory regions by mmap.
1081 /* is_mmap */ true);
1082 if (deep_bucket != NULL)
1083 deep_bucket->committed_size += committed;
1086 profiled_mmap_.AddToVirtualBytes(
1087 mmap_iter->end_addr - mmap_iter->start_addr);
1088 profiled_mmap_.AddToCommittedBytes(committed);
1094 void DeepHeapProfile::WriteProcMaps(const char* prefix,
1098 snprintf(filename, sizeof(filename),
1099 "%s.%05d.maps", prefix, static_cast<int>(getpid()));
1101 RawFD fd = RawOpenForWriting(filename);
1102 RAW_DCHECK(fd != kIllegalRawFD, "");
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);
1112 #else // USE_DEEP_HEAP_PROFILE
1114 DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
1116 : heap_profile_(heap_profile) {
1119 DeepHeapProfile::~DeepHeapProfile() {
1122 int DeepHeapProfile::DumpOrderedProfile(const char* reason,
1128 #endif // USE_DEEP_HEAP_PROFILE