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__)
33 #if defined(COMPILER_MSVC)
34 #include <Winsock2.h> // for gethostname
35 #endif // defined(COMPILER_MSVC)
37 #include "base/cycleclock.h"
38 #include "base/sysinfo.h"
39 #include "internal_logging.h" // for ASSERT, etc
41 static const int kProfilerBufferSize
= 1 << 20;
42 static const int kHashTableSize
= 179999; // Same as heap-profile-table.cc.
44 static const int PAGEMAP_BYTES
= 8;
45 static const int KPAGECOUNT_BYTES
= 8;
46 static const uint64 MAX_ADDRESS
= kuint64max
;
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";
57 static const char kVirtualLabel
[] = "virtual";
58 static const char kCommittedLabel
[] = "committed";
60 #if defined(__linux__)
61 #define OS_NAME "linux"
62 #elif defined(_WIN32) || defined(_WIN64)
63 #define OS_NAME "windows"
65 #define OS_NAME "unknown-os"
68 bool DeepHeapProfile::AppendCommandLine(TextBuffer
* buffer
) {
69 #if defined(__linux__)
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_VLOG(0, "Failed to open /proc/self/cmdline");
81 size_t length
= read(fd
, cmdline
, sizeof(cmdline
) - 1);
84 for (int i
= 0; i
< length
; ++i
)
85 if (cmdline
[i
] == '\0')
87 cmdline
[length
] = '\0';
89 buffer
->AppendString("CommandLine: ", 0);
90 buffer
->AppendString(cmdline
, 0);
91 buffer
->AppendChar('\n');
99 #if defined(_WIN32) || defined(_WIN64)
101 // TODO(peria): Implement this function.
102 void DeepHeapProfile::MemoryInfoGetterWindows::Initialize() {
105 // TODO(peria): Implement this function.
106 size_t DeepHeapProfile::MemoryInfoGetterWindows::CommittedSize(
107 uint64 first_address
,
109 TextBuffer
* buffer
) const {
113 // TODO(peria): Implement this function.
114 bool DeepHeapProfile::MemoryInfoGetterWindows::IsPageCountAvailable() const {
118 #endif // defined(_WIN32) || defined(_WIN64)
120 #if defined(__linux__)
122 void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() {
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");
129 if (pageframe_type_
== DUMP_PAGECOUNT
) {
130 snprintf(filename
, sizeof(filename
), "/proc/kpagecount");
131 kpagecount_fd_
= open(filename
, O_RDONLY
);
132 if (kpagecount_fd_
== -1)
133 RAW_VLOG(0, "Failed to open /proc/kpagecount");
137 size_t DeepHeapProfile::MemoryInfoGetterLinux::CommittedSize(
138 uint64 first_address
,
140 DeepHeapProfile::TextBuffer
* buffer
) const {
141 int page_size
= getpagesize();
142 uint64 page_address
= (first_address
/ page_size
) * page_size
;
143 size_t committed_size
= 0;
144 size_t pageframe_list_length
= 0;
148 // Check every page on which the allocation resides.
149 while (page_address
<= last_address
) {
150 // Read corresponding physical page.
152 // TODO(dmikurube): Read pagemap in bulk for speed.
153 // TODO(dmikurube): Consider using mincore(2).
154 if (Read(&state
, pageframe_type_
!= DUMP_NO_PAGEFRAME
) == false) {
155 // We can't read the last region (e.g vsyscall).
157 RAW_VLOG(0, "pagemap read failed @ %#llx %" PRId64
" bytes",
158 first_address
, last_address
- first_address
+ 1);
163 // Dump pageframes of resident pages. Non-resident pages are just skipped.
164 if (pageframe_type_
!= DUMP_NO_PAGEFRAME
&&
165 buffer
!= NULL
&& state
.pfn
!= 0) {
166 if (pageframe_list_length
== 0) {
167 buffer
->AppendString(" PF:", 0);
168 pageframe_list_length
= 5;
170 buffer
->AppendChar(' ');
171 if (page_address
< first_address
)
172 buffer
->AppendChar('<');
173 buffer
->AppendBase64(state
.pfn
, 4);
174 pageframe_list_length
+= 5;
175 if (pageframe_type_
== DUMP_PAGECOUNT
&& IsPageCountAvailable()) {
176 uint64 pagecount
= ReadPageCount(state
.pfn
);
177 // Assume pagecount == 63 if the pageframe is mapped more than 63 times.
180 buffer
->AppendChar('#');
181 buffer
->AppendBase64(pagecount
, 1);
182 pageframe_list_length
+= 2;
184 if (last_address
< page_address
- 1 + page_size
)
185 buffer
->AppendChar('>');
186 // Begins a new line every 94 characters.
187 if (pageframe_list_length
> 94) {
188 buffer
->AppendChar('\n');
189 pageframe_list_length
= 0;
193 if (state
.is_committed
) {
194 // Calculate the size of the allocation part in this page.
195 size_t bytes
= page_size
;
197 // If looking at the last page in a given region.
198 if (last_address
<= page_address
- 1 + page_size
) {
199 bytes
= last_address
- page_address
+ 1;
202 // If looking at the first page in a given region.
203 if (page_address
< first_address
) {
204 bytes
-= first_address
- page_address
;
207 committed_size
+= bytes
;
209 if (page_address
> MAX_ADDRESS
- page_size
) {
212 page_address
+= page_size
;
215 if (pageframe_type_
!= DUMP_NO_PAGEFRAME
&&
216 buffer
!= NULL
&& pageframe_list_length
!= 0) {
217 buffer
->AppendChar('\n');
220 return committed_size
;
223 uint64
DeepHeapProfile::MemoryInfoGetterLinux::ReadPageCount(uint64 pfn
) const {
224 int64 index
= pfn
* KPAGECOUNT_BYTES
;
225 int64 offset
= lseek64(kpagecount_fd_
, index
, SEEK_SET
);
226 RAW_DCHECK(offset
== index
, "Failed in seeking in kpagecount.");
228 uint64 kpagecount_value
;
229 int result
= read(kpagecount_fd_
, &kpagecount_value
, KPAGECOUNT_BYTES
);
230 if (result
!= KPAGECOUNT_BYTES
)
233 return kpagecount_value
;
236 bool DeepHeapProfile::MemoryInfoGetterLinux::Seek(uint64 address
) const {
237 int64 index
= (address
/ getpagesize()) * PAGEMAP_BYTES
;
238 RAW_DCHECK(pagemap_fd_
!= -1, "Failed to seek in /proc/self/pagemap");
239 int64 offset
= lseek64(pagemap_fd_
, index
, SEEK_SET
);
240 RAW_DCHECK(offset
== index
, "Failed in seeking.");
244 bool DeepHeapProfile::MemoryInfoGetterLinux::Read(
245 State
* state
, bool get_pfn
) const {
246 static const uint64 U64_1
= 1;
247 static const uint64 PFN_FILTER
= (U64_1
<< 55) - U64_1
;
248 static const uint64 PAGE_PRESENT
= U64_1
<< 63;
249 static const uint64 PAGE_SWAP
= U64_1
<< 62;
250 static const uint64 PAGE_RESERVED
= U64_1
<< 61;
251 static const uint64 FLAG_NOPAGE
= U64_1
<< 20;
252 static const uint64 FLAG_KSM
= U64_1
<< 21;
253 static const uint64 FLAG_MMAP
= U64_1
<< 11;
255 uint64 pagemap_value
;
256 RAW_DCHECK(pagemap_fd_
!= -1, "Failed to read from /proc/self/pagemap");
257 int result
= read(pagemap_fd_
, &pagemap_value
, PAGEMAP_BYTES
);
258 if (result
!= PAGEMAP_BYTES
) {
262 // Check if the page is committed.
263 state
->is_committed
= (pagemap_value
& (PAGE_PRESENT
| PAGE_SWAP
));
265 state
->is_present
= (pagemap_value
& PAGE_PRESENT
);
266 state
->is_swapped
= (pagemap_value
& PAGE_SWAP
);
267 state
->is_shared
= false;
269 if (get_pfn
&& state
->is_present
&& !state
->is_swapped
)
270 state
->pfn
= (pagemap_value
& PFN_FILTER
);
277 bool DeepHeapProfile::MemoryInfoGetterLinux::IsPageCountAvailable() const {
278 return kpagecount_fd_
!= -1;
281 #endif // defined(__linux__)
283 DeepHeapProfile::MemoryResidenceInfoGetterInterface::
284 MemoryResidenceInfoGetterInterface() {}
286 DeepHeapProfile::MemoryResidenceInfoGetterInterface::
287 ~MemoryResidenceInfoGetterInterface() {}
289 DeepHeapProfile::MemoryResidenceInfoGetterInterface
*
290 DeepHeapProfile::MemoryResidenceInfoGetterInterface::Create(
291 PageFrameType pageframe_type
) {
292 #if defined(_WIN32) || defined(_WIN64)
293 return new MemoryInfoGetterWindows(pageframe_type
);
294 #elif defined(__linux__)
295 return new MemoryInfoGetterLinux(pageframe_type
);
301 DeepHeapProfile::DeepHeapProfile(HeapProfileTable
* heap_profile
,
303 enum PageFrameType pageframe_type
)
304 : memory_residence_info_getter_(
305 MemoryResidenceInfoGetterInterface::Create(pageframe_type
)),
306 most_recent_pid_(-1),
309 filename_prefix_(NULL
),
310 deep_table_(kHashTableSize
, heap_profile
->alloc_
, heap_profile
->dealloc_
),
311 pageframe_type_(pageframe_type
),
312 heap_profile_(heap_profile
) {
313 // Copy filename prefix.
314 const int prefix_length
= strlen(prefix
);
316 reinterpret_cast<char*>(heap_profile_
->alloc_(prefix_length
+ 1));
317 memcpy(filename_prefix_
, prefix
, prefix_length
);
318 filename_prefix_
[prefix_length
] = '\0';
320 strncpy(run_id_
, "undetermined-run-id", sizeof(run_id_
));
323 DeepHeapProfile::~DeepHeapProfile() {
324 heap_profile_
->dealloc_(filename_prefix_
);
325 delete memory_residence_info_getter_
;
328 // Global malloc() should not be used in this function.
329 // Use LowLevelAlloc if required.
330 void DeepHeapProfile::DumpOrderedProfile(const char* reason
,
334 TextBuffer
buffer(raw_buffer
, buffer_size
, fd
);
337 int64 starting_cycles
= CycleClock::Now();
340 // Get the time before starting snapshot.
341 // TODO(dmikurube): Consider gettimeofday if available.
342 time_t time_value
= time(NULL
);
346 // Re-open files in /proc/pid/ if the process is newly forked one.
347 if (most_recent_pid_
!= getpid()) {
349 if (0 == gethostname(hostname
, sizeof(hostname
))) {
350 char* dot
= strchr(hostname
, '.');
354 strcpy(hostname
, "unknown");
357 most_recent_pid_
= getpid();
359 snprintf(run_id_
, sizeof(run_id_
), "%s-" OS_NAME
"-%d-%lu",
360 hostname
, most_recent_pid_
, time(NULL
));
362 if (memory_residence_info_getter_
)
363 memory_residence_info_getter_
->Initialize();
364 deep_table_
.ResetIsLogged();
366 // Write maps into "|filename_prefix_|.<pid>.maps".
367 WriteProcMaps(filename_prefix_
, raw_buffer
, buffer_size
);
370 // Reset committed sizes of buckets.
371 deep_table_
.ResetCommittedSize();
373 // Record committed sizes.
374 stats_
.SnapshotAllocations(this);
376 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
377 // glibc's snprintf internally allocates memory by alloca normally, but it
378 // allocates memory by malloc if large memory is required.
380 buffer
.AppendString(kProfileHeader
, 0);
381 buffer
.AppendString(kProfileVersion
, 0);
382 buffer
.AppendString("\n", 0);
384 // Fill buffer with meta information.
385 buffer
.AppendString(kMetaInformationHeader
, 0);
387 buffer
.AppendString("Time: ", 0);
388 buffer
.AppendUnsignedLong(time_value
, 0);
389 buffer
.AppendChar('\n');
391 if (reason
!= NULL
) {
392 buffer
.AppendString("Reason: ", 0);
393 buffer
.AppendString(reason
, 0);
394 buffer
.AppendChar('\n');
397 AppendCommandLine(&buffer
);
399 buffer
.AppendString("RunID: ", 0);
400 buffer
.AppendString(run_id_
, 0);
401 buffer
.AppendChar('\n');
403 buffer
.AppendString("PageSize: ", 0);
404 buffer
.AppendInt(getpagesize(), 0, 0);
405 buffer
.AppendChar('\n');
407 // Assumes the physical memory <= 64GB (PFN < 2^24).
408 if (pageframe_type_
== DUMP_PAGECOUNT
&& memory_residence_info_getter_
&&
409 memory_residence_info_getter_
->IsPageCountAvailable()) {
410 buffer
.AppendString("PageFrame: 24,Base64,PageCount", 0);
411 buffer
.AppendChar('\n');
412 } else if (pageframe_type_
!= DUMP_NO_PAGEFRAME
) {
413 buffer
.AppendString("PageFrame: 24,Base64", 0);
414 buffer
.AppendChar('\n');
417 // Fill buffer with the global stats.
418 buffer
.AppendString(kMMapListHeader
, 0);
420 stats_
.SnapshotMaps(memory_residence_info_getter_
, this, &buffer
);
422 // Fill buffer with the global stats.
423 buffer
.AppendString(kGlobalStatsHeader
, 0);
425 stats_
.Unparse(&buffer
);
427 buffer
.AppendString(kStacktraceHeader
, 0);
428 buffer
.AppendString(kVirtualLabel
, 10);
429 buffer
.AppendChar(' ');
430 buffer
.AppendString(kCommittedLabel
, 10);
431 buffer
.AppendString("\n", 0);
434 deep_table_
.UnparseForStats(&buffer
);
438 // Write the bucket listing into a .bucket file.
439 deep_table_
.WriteForBucketFile(
440 filename_prefix_
, dump_count_
, raw_buffer
, buffer_size
);
443 int64 elapsed_cycles
= CycleClock::Now() - starting_cycles
;
444 double elapsed_seconds
= elapsed_cycles
/ CyclesPerSecond();
445 RAW_VLOG(0, "Time spent on DeepProfiler: %.3f sec\n", elapsed_seconds
);
449 int DeepHeapProfile::TextBuffer::Size() {
453 int DeepHeapProfile::TextBuffer::FilledBytes() {
457 void DeepHeapProfile::TextBuffer::Clear() {
461 void DeepHeapProfile::TextBuffer::Flush() {
462 RawWrite(fd_
, buffer_
, cursor_
);
466 // TODO(dmikurube): These Append* functions should not use snprintf.
467 bool DeepHeapProfile::TextBuffer::AppendChar(char value
) {
468 return ForwardCursor(snprintf(buffer_
+ cursor_
, size_
- cursor_
,
472 bool DeepHeapProfile::TextBuffer::AppendString(const char* value
, int width
) {
473 char* position
= buffer_
+ cursor_
;
474 int available
= size_
- cursor_
;
477 appended
= snprintf(position
, available
, "%s", value
);
479 appended
= snprintf(position
, available
, "%*s",
481 return ForwardCursor(appended
);
484 bool DeepHeapProfile::TextBuffer::AppendInt(int value
, int width
,
486 char* position
= buffer_
+ cursor_
;
487 int available
= size_
- cursor_
;
490 appended
= snprintf(position
, available
, "%d", value
);
491 else if (leading_zero
)
492 appended
= snprintf(position
, available
, "%0*d", width
, value
);
494 appended
= snprintf(position
, available
, "%*d", width
, value
);
495 return ForwardCursor(appended
);
498 bool DeepHeapProfile::TextBuffer::AppendLong(long value
, int width
) {
499 char* position
= buffer_
+ cursor_
;
500 int available
= size_
- cursor_
;
503 appended
= snprintf(position
, available
, "%ld", value
);
505 appended
= snprintf(position
, available
, "%*ld", width
, value
);
506 return ForwardCursor(appended
);
509 bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value
,
511 char* position
= buffer_
+ cursor_
;
512 int available
= size_
- cursor_
;
515 appended
= snprintf(position
, available
, "%lu", value
);
517 appended
= snprintf(position
, available
, "%*lu", width
, value
);
518 return ForwardCursor(appended
);
521 bool DeepHeapProfile::TextBuffer::AppendInt64(int64 value
, int width
) {
522 char* position
= buffer_
+ cursor_
;
523 int available
= size_
- cursor_
;
526 appended
= snprintf(position
, available
, "%" PRId64
, value
);
528 appended
= snprintf(position
, available
, "%*" PRId64
, width
, value
);
529 return ForwardCursor(appended
);
532 bool DeepHeapProfile::TextBuffer::AppendPtr(uint64 value
, int width
) {
533 char* position
= buffer_
+ cursor_
;
534 int available
= size_
- cursor_
;
537 appended
= snprintf(position
, available
, "%" PRIx64
, value
);
539 appended
= snprintf(position
, available
, "%0*" PRIx64
, width
, value
);
540 return ForwardCursor(appended
);
543 bool DeepHeapProfile::TextBuffer::AppendBase64(uint64 value
, int width
) {
544 static const char base64
[65] =
545 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
546 #if defined(__BIG_ENDIAN__)
547 value
= bswap_64(value
);
549 for (int shift
= (width
- 1) * 6; shift
>= 0; shift
-= 6) {
550 if (!AppendChar(base64
[(value
>> shift
) & 0x3f]))
556 bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended
) {
557 if (appended
< 0 || appended
>= size_
- cursor_
)
560 if (cursor_
> size_
* 4 / 5)
565 void DeepHeapProfile::DeepBucket::UnparseForStats(TextBuffer
* buffer
) {
566 buffer
->AppendInt64(bucket
->alloc_size
- bucket
->free_size
, 10);
567 buffer
->AppendChar(' ');
568 buffer
->AppendInt64(committed_size
, 10);
569 buffer
->AppendChar(' ');
570 buffer
->AppendInt(bucket
->allocs
, 6, false);
571 buffer
->AppendChar(' ');
572 buffer
->AppendInt(bucket
->frees
, 6, false);
573 buffer
->AppendString(" @ ", 0);
574 buffer
->AppendInt(id
, 0, false);
575 buffer
->AppendString("\n", 0);
578 void DeepHeapProfile::DeepBucket::UnparseForBucketFile(TextBuffer
* buffer
) {
579 buffer
->AppendInt(id
, 0, false);
580 buffer
->AppendChar(' ');
581 buffer
->AppendString(is_mmap
? "mmap" : "malloc", 0);
583 #if defined(TYPE_PROFILING)
584 buffer
->AppendString(" t0x", 0);
585 buffer
->AppendPtr(reinterpret_cast<uintptr_t>(type
), 0);
587 buffer
->AppendString(" nno_typeinfo", 0);
589 buffer
->AppendString(" n", 0);
590 buffer
->AppendString(type
->name(), 0);
594 for (int depth
= 0; depth
< bucket
->depth
; depth
++) {
595 buffer
->AppendString(" 0x", 0);
596 buffer
->AppendPtr(reinterpret_cast<uintptr_t>(bucket
->stack
[depth
]), 8);
598 buffer
->AppendString("\n", 0);
601 DeepHeapProfile::DeepBucketTable::DeepBucketTable(
603 HeapProfileTable::Allocator alloc
,
604 HeapProfileTable::DeAllocator dealloc
)
606 table_size_(table_size
),
610 const int bytes
= table_size
* sizeof(DeepBucket
*);
611 table_
= reinterpret_cast<DeepBucket
**>(alloc(bytes
));
612 memset(table_
, 0, bytes
);
615 DeepHeapProfile::DeepBucketTable::~DeepBucketTable() {
616 ASSERT(table_
!= NULL
);
617 for (int db
= 0; db
< table_size_
; db
++) {
618 for (DeepBucket
* x
= table_
[db
]; x
!= 0; /**/) {
627 DeepHeapProfile::DeepBucket
* DeepHeapProfile::DeepBucketTable::Lookup(
629 #if defined(TYPE_PROFILING)
630 const std::type_info
* type
,
636 AddToHashValue(reinterpret_cast<uintptr_t>(bucket
), &h
);
638 AddToHashValue(1, &h
);
640 AddToHashValue(0, &h
);
643 #if defined(TYPE_PROFILING)
645 AddToHashValue(0, &h
);
647 AddToHashValue(reinterpret_cast<uintptr_t>(type
->name()), &h
);
653 // Lookup stack trace in table
654 unsigned int buck
= ((unsigned int) h
) % table_size_
;
655 for (DeepBucket
* db
= table_
[buck
]; db
!= 0; db
= db
->next
) {
656 if (db
->bucket
== bucket
) {
661 // Create a new bucket
662 DeepBucket
* db
= reinterpret_cast<DeepBucket
*>(alloc_(sizeof(DeepBucket
)));
663 memset(db
, 0, sizeof(*db
));
665 #if defined(TYPE_PROFILING)
668 db
->committed_size
= 0;
669 db
->is_mmap
= is_mmap
;
670 db
->id
= (bucket_id_
++);
671 db
->is_logged
= false;
672 db
->next
= table_
[buck
];
677 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
678 void DeepHeapProfile::DeepBucketTable::UnparseForStats(TextBuffer
* buffer
) {
679 for (int i
= 0; i
< table_size_
; i
++) {
680 for (DeepBucket
* deep_bucket
= table_
[i
];
682 deep_bucket
= deep_bucket
->next
) {
683 Bucket
* bucket
= deep_bucket
->bucket
;
684 if (bucket
->alloc_size
- bucket
->free_size
== 0) {
685 continue; // Skip empty buckets.
687 deep_bucket
->UnparseForStats(buffer
);
692 void DeepHeapProfile::DeepBucketTable::WriteForBucketFile(
693 const char* prefix
, int dump_count
, char raw_buffer
[], int buffer_size
) {
695 snprintf(filename
, sizeof(filename
),
696 "%s.%05d.%04d.buckets", prefix
, getpid(), dump_count
);
697 RawFD fd
= RawOpenForWriting(filename
);
698 RAW_DCHECK(fd
!= kIllegalRawFD
, "");
700 TextBuffer
buffer(raw_buffer
, buffer_size
, fd
);
702 for (int i
= 0; i
< table_size_
; i
++) {
703 for (DeepBucket
* deep_bucket
= table_
[i
];
705 deep_bucket
= deep_bucket
->next
) {
706 Bucket
* bucket
= deep_bucket
->bucket
;
707 if (deep_bucket
->is_logged
) {
708 continue; // Skip the bucket if it is already logged.
710 if (!deep_bucket
->is_mmap
&&
711 bucket
->alloc_size
- bucket
->free_size
<= 64) {
712 continue; // Skip small malloc buckets.
715 deep_bucket
->UnparseForBucketFile(&buffer
);
716 deep_bucket
->is_logged
= true;
724 void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() {
725 for (int i
= 0; i
< table_size_
; i
++) {
726 for (DeepBucket
* deep_bucket
= table_
[i
];
728 deep_bucket
= deep_bucket
->next
) {
729 deep_bucket
->committed_size
= 0;
734 void DeepHeapProfile::DeepBucketTable::ResetIsLogged() {
735 for (int i
= 0; i
< table_size_
; i
++) {
736 for (DeepBucket
* deep_bucket
= table_
[i
];
738 deep_bucket
= deep_bucket
->next
) {
739 deep_bucket
->is_logged
= false;
744 // This hash function is from HeapProfileTable::GetBucket.
746 void DeepHeapProfile::DeepBucketTable::AddToHashValue(
747 uintptr_t add
, uintptr_t* hash_value
) {
749 *hash_value
+= *hash_value
<< 10;
750 *hash_value
^= *hash_value
>> 6;
753 // This hash function is from HeapProfileTable::GetBucket.
755 void DeepHeapProfile::DeepBucketTable::FinishHashValue(uintptr_t* hash_value
) {
756 *hash_value
+= *hash_value
<< 3;
757 *hash_value
^= *hash_value
>> 11;
760 void DeepHeapProfile::RegionStats::Initialize() {
762 committed_bytes_
= 0;
765 uint64
DeepHeapProfile::RegionStats::Record(
766 const MemoryResidenceInfoGetterInterface
* memory_residence_info_getter
,
767 uint64 first_address
,
769 TextBuffer
* buffer
) {
770 uint64 committed
= 0;
771 virtual_bytes_
+= static_cast<size_t>(last_address
- first_address
+ 1);
772 if (memory_residence_info_getter
)
773 committed
= memory_residence_info_getter
->CommittedSize(first_address
,
776 committed_bytes_
+= committed
;
780 void DeepHeapProfile::RegionStats::Unparse(const char* name
,
781 TextBuffer
* buffer
) {
782 buffer
->AppendString(name
, 25);
783 buffer
->AppendChar(' ');
784 buffer
->AppendLong(virtual_bytes_
, 12);
785 buffer
->AppendChar(' ');
786 buffer
->AppendLong(committed_bytes_
, 12);
787 buffer
->AppendString("\n", 0);
790 // Snapshots all virtual memory mapping stats by merging mmap(2) records from
791 // MemoryRegionMap and /proc/maps, the OS-level memory mapping information.
792 // Memory regions described in /proc/maps, but which are not created by mmap,
793 // are accounted as "unhooked" memory regions.
795 // This function assumes that every memory region created by mmap is covered
796 // by VMA(s) described in /proc/maps except for http://crbug.com/189114.
797 // Note that memory regions created with mmap don't align with borders of VMAs
798 // in /proc/maps. In other words, a memory region by mmap can cut across many
799 // VMAs. Also, of course a VMA can include many memory regions by mmap.
800 // It means that the following situation happens:
802 // => Virtual address
803 // <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 ->
804 // ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->..
806 // It can happen easily as permission can be changed by mprotect(2) for a part
807 // of a memory region. A change in permission splits VMA(s).
809 // To deal with the situation, this function iterates over MemoryRegionMap and
810 // /proc/maps independently. The iterator for MemoryRegionMap is initialized
811 // at the top outside the loop for /proc/maps, and it goes forward inside the
812 // loop while comparing their addresses.
814 // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf.
815 void DeepHeapProfile::GlobalStats::SnapshotMaps(
816 const MemoryResidenceInfoGetterInterface
* memory_residence_info_getter
,
817 DeepHeapProfile
* deep_profile
,
818 TextBuffer
* mmap_dump_buffer
) {
819 MemoryRegionMap::LockHolder lock_holder
;
820 ProcMapsIterator::Buffer procmaps_iter_buffer
;
821 ProcMapsIterator
procmaps_iter(0, &procmaps_iter_buffer
);
822 uint64 vma_start_addr
, vma_last_addr
, offset
;
826 enum MapsRegionType type
;
828 for (int i
= 0; i
< NUMBER_OF_MAPS_REGION_TYPES
; ++i
) {
829 all_
[i
].Initialize();
830 unhooked_
[i
].Initialize();
832 profiled_mmap_
.Initialize();
834 MemoryRegionMap::RegionIterator mmap_iter
=
835 MemoryRegionMap::BeginRegionLocked();
836 DeepBucket
* deep_bucket
= NULL
;
837 if (mmap_iter
!= MemoryRegionMap::EndRegionLocked()) {
838 deep_bucket
= GetInformationOfMemoryRegion(
839 mmap_iter
, memory_residence_info_getter
, deep_profile
);
842 while (procmaps_iter
.Next(&vma_start_addr
, &vma_last_addr
,
843 &flags
, &offset
, &inode
, &filename
)) {
844 if (mmap_dump_buffer
) {
846 int written
= procmaps_iter
.FormatLine(buffer
, sizeof(buffer
),
847 vma_start_addr
, vma_last_addr
,
848 flags
, offset
, inode
, filename
, 0);
849 mmap_dump_buffer
->AppendString(buffer
, 0);
852 // 'vma_last_addr' should be the last inclusive address of the region.
854 if (strcmp("[vsyscall]", filename
) == 0) {
855 continue; // Reading pagemap will fail in [vsyscall].
858 // TODO(dmikurube): |type| will be deprecated in the dump.
859 // See http://crbug.com/245603.
861 if (filename
[0] == '/') {
866 } else if (filename
[0] == '\0' || filename
[0] == '\n') {
868 } else if (strcmp(filename
, "[stack]") == 0) {
873 // TODO(dmikurube): This |all_| count should be removed in future soon.
874 // See http://crbug.com/245603.
875 uint64 vma_total
= all_
[type
].Record(
876 memory_residence_info_getter
, vma_start_addr
, vma_last_addr
, NULL
);
877 uint64 vma_subtotal
= 0;
879 // TODO(dmikurube): Stop double-counting pagemap.
880 // It will be fixed when http://crbug.com/245603 finishes.
881 if (MemoryRegionMap::IsRecordingLocked()) {
882 uint64 cursor
= vma_start_addr
;
885 // Iterates over MemoryRegionMap until the iterator moves out of the VMA.
888 cursor
= mmap_iter
->end_addr
;
890 // Don't break here even if mmap_iter == EndRegionLocked().
892 if (mmap_iter
!= MemoryRegionMap::EndRegionLocked()) {
893 deep_bucket
= GetInformationOfMemoryRegion(
894 mmap_iter
, memory_residence_info_getter
, deep_profile
);
899 uint64 last_address_of_unhooked
;
900 // If the next mmap entry is away from the current VMA.
901 if (mmap_iter
== MemoryRegionMap::EndRegionLocked() ||
902 mmap_iter
->start_addr
> vma_last_addr
) {
903 last_address_of_unhooked
= vma_last_addr
;
905 last_address_of_unhooked
= mmap_iter
->start_addr
- 1;
908 if (last_address_of_unhooked
+ 1 > cursor
) {
909 RAW_CHECK(cursor
>= vma_start_addr
,
910 "Wrong calculation for unhooked");
911 RAW_CHECK(last_address_of_unhooked
<= vma_last_addr
,
912 "Wrong calculation for unhooked");
913 uint64 committed_size
= unhooked_
[type
].Record(
914 memory_residence_info_getter
,
916 last_address_of_unhooked
,
918 vma_subtotal
+= committed_size
;
919 if (mmap_dump_buffer
) {
920 mmap_dump_buffer
->AppendString(" ", 0);
921 mmap_dump_buffer
->AppendPtr(cursor
, 0);
922 mmap_dump_buffer
->AppendString(" - ", 0);
923 mmap_dump_buffer
->AppendPtr(last_address_of_unhooked
+ 1, 0);
924 mmap_dump_buffer
->AppendString(" unhooked ", 0);
925 mmap_dump_buffer
->AppendInt64(committed_size
, 0);
926 mmap_dump_buffer
->AppendString(" / ", 0);
927 mmap_dump_buffer
->AppendInt64(
928 last_address_of_unhooked
- cursor
+ 1, 0);
929 mmap_dump_buffer
->AppendString("\n", 0);
931 cursor
= last_address_of_unhooked
+ 1;
934 if (mmap_iter
!= MemoryRegionMap::EndRegionLocked() &&
935 mmap_iter
->start_addr
<= vma_last_addr
&&
937 bool trailing
= mmap_iter
->start_addr
< vma_start_addr
;
938 bool continued
= mmap_iter
->end_addr
- 1 > vma_last_addr
;
939 uint64 partial_first_address
, partial_last_address
;
941 partial_first_address
= vma_start_addr
;
943 partial_first_address
= mmap_iter
->start_addr
;
945 partial_last_address
= vma_last_addr
;
947 partial_last_address
= mmap_iter
->end_addr
- 1;
948 uint64 committed_size
= 0;
949 if (memory_residence_info_getter
)
950 committed_size
= memory_residence_info_getter
->CommittedSize(
951 partial_first_address
, partial_last_address
, mmap_dump_buffer
);
952 vma_subtotal
+= committed_size
;
953 mmap_dump_buffer
->AppendString(trailing
? " (" : " ", 0);
954 mmap_dump_buffer
->AppendPtr(mmap_iter
->start_addr
, 0);
955 mmap_dump_buffer
->AppendString(trailing
? ")" : " ", 0);
956 mmap_dump_buffer
->AppendString("-", 0);
957 mmap_dump_buffer
->AppendString(continued
? "(" : " ", 0);
958 mmap_dump_buffer
->AppendPtr(mmap_iter
->end_addr
, 0);
959 mmap_dump_buffer
->AppendString(continued
? ")" : " ", 0);
960 mmap_dump_buffer
->AppendString(" hooked ", 0);
961 mmap_dump_buffer
->AppendInt64(committed_size
, 0);
962 mmap_dump_buffer
->AppendString(" / ", 0);
963 mmap_dump_buffer
->AppendInt64(
964 partial_last_address
- partial_first_address
+ 1, 0);
965 mmap_dump_buffer
->AppendString(" @ ", 0);
966 if (deep_bucket
!= NULL
) {
967 mmap_dump_buffer
->AppendInt(deep_bucket
->id
, 0, false);
969 mmap_dump_buffer
->AppendInt(0, 0, false);
971 mmap_dump_buffer
->AppendString("\n", 0);
973 } while (mmap_iter
!= MemoryRegionMap::EndRegionLocked() &&
974 mmap_iter
->end_addr
- 1 <= vma_last_addr
);
977 if (vma_total
!= vma_subtotal
) {
979 int written
= procmaps_iter
.FormatLine(buffer
, sizeof(buffer
),
980 vma_start_addr
, vma_last_addr
,
981 flags
, offset
, inode
, filename
, 0);
982 RAW_VLOG(0, "[%d] Mismatched total in VMA %" PRId64
":"
983 "%" PRId64
" (%" PRId64
")",
984 getpid(), vma_total
, vma_subtotal
, vma_total
- vma_subtotal
);
985 RAW_VLOG(0, "[%d] in %s", getpid(), buffer
);
989 // TODO(dmikurube): Investigate and fix http://crbug.com/189114.
991 // The total committed memory usage in all_ (from /proc/<pid>/maps) is
992 // sometimes smaller than the sum of the committed mmap'ed addresses and
993 // unhooked regions. Within our observation, the difference was only 4KB
994 // in committed usage, zero in reserved virtual addresses
996 // A guess is that an uncommitted (but reserved) page may become committed
997 // during counting memory usage in the loop above.
999 // The difference is accounted as "ABSENT" to investigate such cases.
1001 // It will be fixed when http://crbug.com/245603 finishes (no double count).
1003 RegionStats all_total
;
1004 RegionStats unhooked_total
;
1005 for (int i
= 0; i
< NUMBER_OF_MAPS_REGION_TYPES
; ++i
) {
1006 all_total
.AddAnotherRegionStat(all_
[i
]);
1007 unhooked_total
.AddAnotherRegionStat(unhooked_
[i
]);
1010 size_t absent_virtual
= profiled_mmap_
.virtual_bytes() +
1011 unhooked_total
.virtual_bytes() -
1012 all_total
.virtual_bytes();
1013 if (absent_virtual
> 0)
1014 all_
[ABSENT
].AddToVirtualBytes(absent_virtual
);
1016 size_t absent_committed
= profiled_mmap_
.committed_bytes() +
1017 unhooked_total
.committed_bytes() -
1018 all_total
.committed_bytes();
1019 if (absent_committed
> 0)
1020 all_
[ABSENT
].AddToCommittedBytes(absent_committed
);
1023 void DeepHeapProfile::GlobalStats::SnapshotAllocations(
1024 DeepHeapProfile
* deep_profile
) {
1025 profiled_malloc_
.Initialize();
1027 deep_profile
->heap_profile_
->address_map_
->Iterate(RecordAlloc
, deep_profile
);
1030 void DeepHeapProfile::GlobalStats::Unparse(TextBuffer
* buffer
) {
1031 RegionStats all_total
;
1032 RegionStats unhooked_total
;
1033 for (int i
= 0; i
< NUMBER_OF_MAPS_REGION_TYPES
; ++i
) {
1034 all_total
.AddAnotherRegionStat(all_
[i
]);
1035 unhooked_total
.AddAnotherRegionStat(unhooked_
[i
]);
1038 // "# total (%lu) %c= profiled-mmap (%lu) + nonprofiled-* (%lu)\n"
1039 buffer
->AppendString("# total (", 0);
1040 buffer
->AppendUnsignedLong(all_total
.committed_bytes(), 0);
1041 buffer
->AppendString(") ", 0);
1042 buffer
->AppendChar(all_total
.committed_bytes() ==
1043 profiled_mmap_
.committed_bytes() +
1044 unhooked_total
.committed_bytes() ? '=' : '!');
1045 buffer
->AppendString("= profiled-mmap (", 0);
1046 buffer
->AppendUnsignedLong(profiled_mmap_
.committed_bytes(), 0);
1047 buffer
->AppendString(") + nonprofiled-* (", 0);
1048 buffer
->AppendUnsignedLong(unhooked_total
.committed_bytes(), 0);
1049 buffer
->AppendString(")\n", 0);
1051 // " virtual committed"
1052 buffer
->AppendString("", 26);
1053 buffer
->AppendString(kVirtualLabel
, 12);
1054 buffer
->AppendChar(' ');
1055 buffer
->AppendString(kCommittedLabel
, 12);
1056 buffer
->AppendString("\n", 0);
1058 all_total
.Unparse("total", buffer
);
1059 all_
[ABSENT
].Unparse("absent", buffer
);
1060 all_
[FILE_EXEC
].Unparse("file-exec", buffer
);
1061 all_
[FILE_NONEXEC
].Unparse("file-nonexec", buffer
);
1062 all_
[ANONYMOUS
].Unparse("anonymous", buffer
);
1063 all_
[STACK
].Unparse("stack", buffer
);
1064 all_
[OTHER
].Unparse("other", buffer
);
1065 unhooked_total
.Unparse("nonprofiled-total", buffer
);
1066 unhooked_
[ABSENT
].Unparse("nonprofiled-absent", buffer
);
1067 unhooked_
[ANONYMOUS
].Unparse("nonprofiled-anonymous", buffer
);
1068 unhooked_
[FILE_EXEC
].Unparse("nonprofiled-file-exec", buffer
);
1069 unhooked_
[FILE_NONEXEC
].Unparse("nonprofiled-file-nonexec", buffer
);
1070 unhooked_
[STACK
].Unparse("nonprofiled-stack", buffer
);
1071 unhooked_
[OTHER
].Unparse("nonprofiled-other", buffer
);
1072 profiled_mmap_
.Unparse("profiled-mmap", buffer
);
1073 profiled_malloc_
.Unparse("profiled-malloc", buffer
);
1077 void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer
,
1078 AllocValue
* alloc_value
,
1079 DeepHeapProfile
* deep_profile
) {
1080 uint64 address
= reinterpret_cast<uintptr_t>(pointer
);
1081 size_t committed
= deep_profile
->memory_residence_info_getter_
->CommittedSize(
1082 address
, address
+ alloc_value
->bytes
- 1, NULL
);
1084 DeepBucket
* deep_bucket
= deep_profile
->deep_table_
.Lookup(
1085 alloc_value
->bucket(),
1086 #if defined(TYPE_PROFILING)
1087 LookupType(pointer
),
1089 /* is_mmap */ false);
1090 deep_bucket
->committed_size
+= committed
;
1091 deep_profile
->stats_
.profiled_malloc_
.AddToVirtualBytes(alloc_value
->bytes
);
1092 deep_profile
->stats_
.profiled_malloc_
.AddToCommittedBytes(committed
);
1095 DeepHeapProfile::DeepBucket
*
1096 DeepHeapProfile::GlobalStats::GetInformationOfMemoryRegion(
1097 const MemoryRegionMap::RegionIterator
& mmap_iter
,
1098 const MemoryResidenceInfoGetterInterface
* memory_residence_info_getter
,
1099 DeepHeapProfile
* deep_profile
) {
1100 size_t committed
= deep_profile
->memory_residence_info_getter_
->
1101 CommittedSize(mmap_iter
->start_addr
, mmap_iter
->end_addr
- 1, NULL
);
1103 // TODO(dmikurube): Store a reference to the bucket in region.
1104 Bucket
* bucket
= MemoryRegionMap::GetBucket(
1105 mmap_iter
->call_stack_depth
, mmap_iter
->call_stack
);
1106 DeepBucket
* deep_bucket
= NULL
;
1107 if (bucket
!= NULL
) {
1108 deep_bucket
= deep_profile
->deep_table_
.Lookup(
1110 #if defined(TYPE_PROFILING)
1111 NULL
, // No type information for memory regions by mmap.
1113 /* is_mmap */ true);
1114 if (deep_bucket
!= NULL
)
1115 deep_bucket
->committed_size
+= committed
;
1118 profiled_mmap_
.AddToVirtualBytes(
1119 mmap_iter
->end_addr
- mmap_iter
->start_addr
);
1120 profiled_mmap_
.AddToCommittedBytes(committed
);
1126 void DeepHeapProfile::WriteProcMaps(const char* prefix
,
1130 snprintf(filename
, sizeof(filename
),
1131 "%s.%05d.maps", prefix
, static_cast<int>(getpid()));
1133 RawFD fd
= RawOpenForWriting(filename
);
1134 RAW_DCHECK(fd
!= kIllegalRawFD
, "");
1138 length
= tcmalloc::FillProcSelfMaps(raw_buffer
, buffer_size
, &wrote_all
);
1139 RAW_DCHECK(wrote_all
, "");
1140 RAW_DCHECK(length
<= buffer_size
, "");
1141 RawWrite(fd
, raw_buffer
, length
);
1144 #else // USE_DEEP_HEAP_PROFILE
1146 DeepHeapProfile::DeepHeapProfile(HeapProfileTable
* heap_profile
,
1148 enum PageFrameType pageframe_type
)
1149 : heap_profile_(heap_profile
) {
1152 DeepHeapProfile::~DeepHeapProfile() {
1155 void DeepHeapProfile::DumpOrderedProfile(const char* reason
,
1161 #endif // USE_DEEP_HEAP_PROFILE