[Session restore] Rename group name Enabled to Restore.
[chromium-blink-merge.git] / third_party / tcmalloc / chromium / src / deep-heap-profile.cc
blobbbf002a6c0563803c1b18eecbe56913f7c961689
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.
5 // ---
6 // Author: Sainbayar Sukhbaatar
7 // Dai Mikurube
8 //
10 #include "deep-heap-profile.h"
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
22 #if defined(__linux__)
23 #include <endian.h>
24 #if !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__)
25 #if __BYTE_ORDER == __BIG_ENDIAN
26 #define __BIG_ENDIAN__
27 #endif // __BYTE_ORDER == __BIG_ENDIAN
28 #endif // !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__)
29 #if defined(__BIG_ENDIAN__)
30 #include <byteswap.h>
31 #endif // defined(__BIG_ENDIAN__)
32 #endif // defined(__linux__)
33 #if defined(COMPILER_MSVC)
34 #include <Winsock2.h> // for gethostname
35 #endif // defined(COMPILER_MSVC)
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"
64 #else
65 #define OS_NAME "unknown-os"
66 #endif
68 bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) {
69 #if defined(__linux__)
70 RawFD fd;
71 char filename[100];
72 char cmdline[4096];
73 snprintf(filename, sizeof(filename), "/proc/%d/cmdline",
74 static_cast<int>(getpid()));
75 fd = open(filename, O_RDONLY);
76 if (fd == kIllegalRawFD) {
77 RAW_VLOG(0, "Failed to open /proc/self/cmdline");
78 return false;
81 size_t length = read(fd, cmdline, sizeof(cmdline) - 1);
82 close(fd);
84 for (int i = 0; i < length; ++i)
85 if (cmdline[i] == '\0')
86 cmdline[i] = ' ';
87 cmdline[length] = '\0';
89 buffer->AppendString("CommandLine: ", 0);
90 buffer->AppendString(cmdline, 0);
91 buffer->AppendChar('\n');
93 return true;
94 #else
95 return false;
96 #endif
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,
108 uint64 last_address,
109 TextBuffer* buffer) const {
110 return 0;
113 // TODO(peria): Implement this function.
114 bool DeepHeapProfile::MemoryInfoGetterWindows::IsPageCountAvailable() const {
115 return false;
118 #endif // defined(_WIN32) || defined(_WIN64)
120 #if defined(__linux__)
122 void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() {
123 char filename[100];
124 snprintf(filename, sizeof(filename), "/proc/%d/pagemap",
125 static_cast<int>(getpid()));
126 pagemap_fd_ = open(filename, O_RDONLY);
127 RAW_CHECK(pagemap_fd_ != -1, "Failed to open /proc/self/pagemap");
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,
139 uint64 last_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;
146 Seek(first_address);
148 // Check every page on which the allocation resides.
149 while (page_address <= last_address) {
150 // Read corresponding physical page.
151 State state;
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).
156 #ifndef NDEBUG
157 RAW_VLOG(0, "pagemap read failed @ %#llx %" PRId64 " bytes",
158 first_address, last_address - first_address + 1);
159 #endif
160 return 0;
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.
178 if (pagecount > 63)
179 pagecount = 63;
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) {
210 break;
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)
231 return 0;
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.");
241 return offset >= 0;
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) {
259 return false;
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);
271 else
272 state->pfn = 0;
274 return true;
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);
296 #else
297 return NULL;
298 #endif
301 DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
302 const char* prefix,
303 enum PageFrameType pageframe_type)
304 : memory_residence_info_getter_(
305 MemoryResidenceInfoGetterInterface::Create(pageframe_type)),
306 most_recent_pid_(-1),
307 stats_(),
308 dump_count_(0),
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);
315 filename_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,
331 char raw_buffer[],
332 int buffer_size,
333 RawFD fd) {
334 TextBuffer buffer(raw_buffer, buffer_size, fd);
336 #ifndef NDEBUG
337 int64 starting_cycles = CycleClock::Now();
338 #endif
340 // Get the time before starting snapshot.
341 // TODO(dmikurube): Consider gettimeofday if available.
342 time_t time_value = time(NULL);
344 ++dump_count_;
346 // Re-open files in /proc/pid/ if the process is newly forked one.
347 if (most_recent_pid_ != getpid()) {
348 char hostname[64];
349 if (0 == gethostname(hostname, sizeof(hostname))) {
350 char* dot = strchr(hostname, '.');
351 if (dot != NULL)
352 *dot = '\0';
353 } else {
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);
433 // Fill buffer.
434 deep_table_.UnparseForStats(&buffer);
436 buffer.Flush();
438 // Write the bucket listing into a .bucket file.
439 deep_table_.WriteForBucketFile(
440 filename_prefix_, dump_count_, raw_buffer, buffer_size);
442 #ifndef NDEBUG
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);
446 #endif
449 int DeepHeapProfile::TextBuffer::Size() {
450 return size_;
453 int DeepHeapProfile::TextBuffer::FilledBytes() {
454 return cursor_;
457 void DeepHeapProfile::TextBuffer::Clear() {
458 cursor_ = 0;
461 void DeepHeapProfile::TextBuffer::Flush() {
462 RawWrite(fd_, buffer_, cursor_);
463 cursor_ = 0;
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_,
469 "%c", value));
472 bool DeepHeapProfile::TextBuffer::AppendString(const char* value, int width) {
473 char* position = buffer_ + cursor_;
474 int available = size_ - cursor_;
475 int appended;
476 if (width == 0)
477 appended = snprintf(position, available, "%s", value);
478 else
479 appended = snprintf(position, available, "%*s",
480 width, value);
481 return ForwardCursor(appended);
484 bool DeepHeapProfile::TextBuffer::AppendInt(int value, int width,
485 bool leading_zero) {
486 char* position = buffer_ + cursor_;
487 int available = size_ - cursor_;
488 int appended;
489 if (width == 0)
490 appended = snprintf(position, available, "%d", value);
491 else if (leading_zero)
492 appended = snprintf(position, available, "%0*d", width, value);
493 else
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_;
501 int appended;
502 if (width == 0)
503 appended = snprintf(position, available, "%ld", value);
504 else
505 appended = snprintf(position, available, "%*ld", width, value);
506 return ForwardCursor(appended);
509 bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value,
510 int width) {
511 char* position = buffer_ + cursor_;
512 int available = size_ - cursor_;
513 int appended;
514 if (width == 0)
515 appended = snprintf(position, available, "%lu", value);
516 else
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_;
524 int appended;
525 if (width == 0)
526 appended = snprintf(position, available, "%" PRId64, value);
527 else
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_;
535 int appended;
536 if (width == 0)
537 appended = snprintf(position, available, "%" PRIx64, value);
538 else
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);
548 #endif
549 for (int shift = (width - 1) * 6; shift >= 0; shift -= 6) {
550 if (!AppendChar(base64[(value >> shift) & 0x3f]))
551 return false;
553 return true;
556 bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended) {
557 if (appended < 0 || appended >= size_ - cursor_)
558 return false;
559 cursor_ += appended;
560 if (cursor_ > size_ * 4 / 5)
561 Flush();
562 return true;
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);
586 if (type == NULL) {
587 buffer->AppendString(" nno_typeinfo", 0);
588 } else {
589 buffer->AppendString(" n", 0);
590 buffer->AppendString(type->name(), 0);
592 #endif
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(
602 int table_size,
603 HeapProfileTable::Allocator alloc,
604 HeapProfileTable::DeAllocator dealloc)
605 : table_(NULL),
606 table_size_(table_size),
607 alloc_(alloc),
608 dealloc_(dealloc),
609 bucket_id_(0) {
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; /**/) {
619 DeepBucket* db = x;
620 x = x->next;
621 dealloc_(db);
624 dealloc_(table_);
627 DeepHeapProfile::DeepBucket* DeepHeapProfile::DeepBucketTable::Lookup(
628 Bucket* bucket,
629 #if defined(TYPE_PROFILING)
630 const std::type_info* type,
631 #endif
632 bool is_mmap) {
633 // Make hash-value
634 uintptr_t h = 0;
636 AddToHashValue(reinterpret_cast<uintptr_t>(bucket), &h);
637 if (is_mmap) {
638 AddToHashValue(1, &h);
639 } else {
640 AddToHashValue(0, &h);
643 #if defined(TYPE_PROFILING)
644 if (type == NULL) {
645 AddToHashValue(0, &h);
646 } else {
647 AddToHashValue(reinterpret_cast<uintptr_t>(type->name()), &h);
649 #endif
651 FinishHashValue(&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) {
657 return db;
661 // Create a new bucket
662 DeepBucket* db = reinterpret_cast<DeepBucket*>(alloc_(sizeof(DeepBucket)));
663 memset(db, 0, sizeof(*db));
664 db->bucket = bucket;
665 #if defined(TYPE_PROFILING)
666 db->type = type;
667 #endif
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];
673 table_[buck] = db;
674 return db;
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];
681 deep_bucket != NULL;
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) {
694 char filename[100];
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];
704 deep_bucket != NULL;
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;
720 buffer.Flush();
721 RawClose(fd);
724 void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() {
725 for (int i = 0; i < table_size_; i++) {
726 for (DeepBucket* deep_bucket = table_[i];
727 deep_bucket != NULL;
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];
737 deep_bucket != NULL;
738 deep_bucket = deep_bucket->next) {
739 deep_bucket->is_logged = false;
744 // This hash function is from HeapProfileTable::GetBucket.
745 // static
746 void DeepHeapProfile::DeepBucketTable::AddToHashValue(
747 uintptr_t add, uintptr_t* hash_value) {
748 *hash_value += add;
749 *hash_value += *hash_value << 10;
750 *hash_value ^= *hash_value >> 6;
753 // This hash function is from HeapProfileTable::GetBucket.
754 // static
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() {
761 virtual_bytes_ = 0;
762 committed_bytes_ = 0;
765 uint64 DeepHeapProfile::RegionStats::Record(
766 const MemoryResidenceInfoGetterInterface* memory_residence_info_getter,
767 uint64 first_address,
768 uint64 last_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,
774 last_address,
775 buffer);
776 committed_bytes_ += committed;
777 return 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;
823 int64 inode;
824 char* flags;
825 char* filename;
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) {
845 char buffer[1024];
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.
853 vma_last_addr -= 1;
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.
860 type = ABSENT;
861 if (filename[0] == '/') {
862 if (flags[2] == 'x')
863 type = FILE_EXEC;
864 else
865 type = FILE_NONEXEC;
866 } else if (filename[0] == '\0' || filename[0] == '\n') {
867 type = ANONYMOUS;
868 } else if (strcmp(filename, "[stack]") == 0) {
869 type = STACK;
870 } else {
871 type = OTHER;
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;
883 bool first = true;
885 // Iterates over MemoryRegionMap until the iterator moves out of the VMA.
886 do {
887 if (!first) {
888 cursor = mmap_iter->end_addr;
889 ++mmap_iter;
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);
897 first = false;
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;
904 } else {
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,
915 cursor,
916 last_address_of_unhooked,
917 mmap_dump_buffer);
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 &&
936 mmap_dump_buffer) {
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;
940 if (trailing)
941 partial_first_address = vma_start_addr;
942 else
943 partial_first_address = mmap_iter->start_addr;
944 if (continued)
945 partial_last_address = vma_last_addr;
946 else
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);
968 } else {
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) {
978 char buffer[1024];
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);
1076 // static
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),
1088 #endif
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(
1109 bucket,
1110 #if defined(TYPE_PROFILING)
1111 NULL, // No type information for memory regions by mmap.
1112 #endif
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);
1122 return deep_bucket;
1125 // static
1126 void DeepHeapProfile::WriteProcMaps(const char* prefix,
1127 char raw_buffer[],
1128 int buffer_size) {
1129 char filename[100];
1130 snprintf(filename, sizeof(filename),
1131 "%s.%05d.maps", prefix, static_cast<int>(getpid()));
1133 RawFD fd = RawOpenForWriting(filename);
1134 RAW_DCHECK(fd != kIllegalRawFD, "");
1136 int length;
1137 bool wrote_all;
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);
1142 RawClose(fd);
1144 #else // USE_DEEP_HEAP_PROFILE
1146 DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile,
1147 const char* prefix,
1148 enum PageFrameType pageframe_type)
1149 : heap_profile_(heap_profile) {
1152 DeepHeapProfile::~DeepHeapProfile() {
1155 void DeepHeapProfile::DumpOrderedProfile(const char* reason,
1156 char raw_buffer[],
1157 int buffer_size,
1158 RawFD fd) {
1161 #endif // USE_DEEP_HEAP_PROFILE