|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | // --- | 
|  | // Author: Sainbayar Sukhbaatar | 
|  | //         Dai Mikurube | 
|  | // | 
|  |  | 
|  | #include "deep-heap-profile.h" | 
|  |  | 
|  | #ifdef USE_DEEP_HEAP_PROFILE | 
|  | #include <algorithm> | 
|  | #include <fcntl.h> | 
|  | #include <sys/stat.h> | 
|  | #include <sys/types.h> | 
|  | #include <time.h> | 
|  | #ifdef HAVE_UNISTD_H | 
|  | #include <unistd.h>  // for getpagesize and getpid | 
|  | #endif  // HAVE_UNISTD_H | 
|  |  | 
|  | #if defined(__linux__) | 
|  | #include <endian.h> | 
|  | #if !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__) | 
|  | #if __BYTE_ORDER == __BIG_ENDIAN | 
|  | #define __BIG_ENDIAN__ | 
|  | #endif  // __BYTE_ORDER == __BIG_ENDIAN | 
|  | #endif  // !defined(__LITTLE_ENDIAN__) and !defined(__BIG_ENDIAN__) | 
|  | #if defined(__BIG_ENDIAN__) | 
|  | #include <byteswap.h> | 
|  | #endif  // defined(__BIG_ENDIAN__) | 
|  | #endif  // defined(__linux__) | 
|  | #if defined(COMPILER_MSVC) | 
|  | #include <Winsock2.h>  // for gethostname | 
|  | #endif  // defined(COMPILER_MSVC) | 
|  |  | 
|  | #include "base/cycleclock.h" | 
|  | #include "base/sysinfo.h" | 
|  | #include "internal_logging.h"  // for ASSERT, etc | 
|  |  | 
|  | static const int kProfilerBufferSize = 1 << 20; | 
|  | static const int kHashTableSize = 179999;  // Same as heap-profile-table.cc. | 
|  |  | 
|  | static const int PAGEMAP_BYTES = 8; | 
|  | static const int KPAGECOUNT_BYTES = 8; | 
|  | static const uint64 MAX_ADDRESS = kuint64max; | 
|  |  | 
|  | // Tag strings in heap profile dumps. | 
|  | static const char kProfileHeader[] = "heap profile: "; | 
|  | static const char kProfileVersion[] = "DUMP_DEEP_6"; | 
|  | static const char kMetaInformationHeader[] = "META:\n"; | 
|  | static const char kMMapListHeader[] = "MMAP_LIST:\n"; | 
|  | static const char kGlobalStatsHeader[] = "GLOBAL_STATS:\n"; | 
|  | static const char kStacktraceHeader[] = "STACKTRACES:\n"; | 
|  | static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n"; | 
|  |  | 
|  | static const char kVirtualLabel[] = "virtual"; | 
|  | static const char kCommittedLabel[] = "committed"; | 
|  |  | 
|  | #if defined(__linux__) | 
|  | #define OS_NAME "linux" | 
|  | #elif defined(_WIN32) || defined(_WIN64) | 
|  | #define OS_NAME "windows" | 
|  | #else | 
|  | #define OS_NAME "unknown-os" | 
|  | #endif | 
|  |  | 
|  | bool DeepHeapProfile::AppendCommandLine(TextBuffer* buffer) { | 
|  | #if defined(__linux__) | 
|  | RawFD fd; | 
|  | char filename[100]; | 
|  | char cmdline[4096]; | 
|  | snprintf(filename, sizeof(filename), "/proc/%d/cmdline", | 
|  | static_cast<int>(getpid())); | 
|  | fd = open(filename, O_RDONLY); | 
|  | if (fd == kIllegalRawFD) { | 
|  | RAW_VLOG(0, "Failed to open /proc/self/cmdline"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | size_t length = read(fd, cmdline, sizeof(cmdline) - 1); | 
|  | close(fd); | 
|  |  | 
|  | for (int i = 0; i < length; ++i) | 
|  | if (cmdline[i] == '\0') | 
|  | cmdline[i] = ' '; | 
|  | cmdline[length] = '\0'; | 
|  |  | 
|  | buffer->AppendString("CommandLine: ", 0); | 
|  | buffer->AppendString(cmdline, 0); | 
|  | buffer->AppendChar('\n'); | 
|  |  | 
|  | return true; | 
|  | #else | 
|  | return false; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | #if defined(_WIN32) || defined(_WIN64) | 
|  |  | 
|  | // TODO(peria): Implement this function. | 
|  | void DeepHeapProfile::MemoryInfoGetterWindows::Initialize() { | 
|  | } | 
|  |  | 
|  | // TODO(peria): Implement this function. | 
|  | size_t DeepHeapProfile::MemoryInfoGetterWindows::CommittedSize( | 
|  | uint64 first_address, | 
|  | uint64 last_address, | 
|  | TextBuffer* buffer) const { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // TODO(peria): Implement this function. | 
|  | bool DeepHeapProfile::MemoryInfoGetterWindows::IsPageCountAvailable() const { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #endif  // defined(_WIN32) || defined(_WIN64) | 
|  |  | 
|  | #if defined(__linux__) | 
|  |  | 
|  | void DeepHeapProfile::MemoryInfoGetterLinux::Initialize() { | 
|  | char filename[100]; | 
|  | snprintf(filename, sizeof(filename), "/proc/%d/pagemap", | 
|  | static_cast<int>(getpid())); | 
|  | pagemap_fd_ = open(filename, O_RDONLY); | 
|  | RAW_CHECK(pagemap_fd_ != -1, "Failed to open /proc/self/pagemap"); | 
|  |  | 
|  | if (pageframe_type_ == DUMP_PAGECOUNT) { | 
|  | snprintf(filename, sizeof(filename), "/proc/kpagecount"); | 
|  | kpagecount_fd_ = open(filename, O_RDONLY); | 
|  | if (kpagecount_fd_ == -1) | 
|  | RAW_VLOG(0, "Failed to open /proc/kpagecount"); | 
|  | } | 
|  | } | 
|  |  | 
|  | size_t DeepHeapProfile::MemoryInfoGetterLinux::CommittedSize( | 
|  | uint64 first_address, | 
|  | uint64 last_address, | 
|  | DeepHeapProfile::TextBuffer* buffer) const { | 
|  | int page_size = getpagesize(); | 
|  | uint64 page_address = (first_address / page_size) * page_size; | 
|  | size_t committed_size = 0; | 
|  | size_t pageframe_list_length = 0; | 
|  |  | 
|  | Seek(first_address); | 
|  |  | 
|  | // Check every page on which the allocation resides. | 
|  | while (page_address <= last_address) { | 
|  | // Read corresponding physical page. | 
|  | State state; | 
|  | // TODO(dmikurube): Read pagemap in bulk for speed. | 
|  | // TODO(dmikurube): Consider using mincore(2). | 
|  | if (Read(&state, pageframe_type_ != DUMP_NO_PAGEFRAME) == false) { | 
|  | // We can't read the last region (e.g vsyscall). | 
|  | #ifndef NDEBUG | 
|  | RAW_VLOG(0, "pagemap read failed @ %#llx %" PRId64 " bytes", | 
|  | first_address, last_address - first_address + 1); | 
|  | #endif | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // Dump pageframes of resident pages.  Non-resident pages are just skipped. | 
|  | if (pageframe_type_ != DUMP_NO_PAGEFRAME && | 
|  | buffer != NULL && state.pfn != 0) { | 
|  | if (pageframe_list_length == 0) { | 
|  | buffer->AppendString("  PF:", 0); | 
|  | pageframe_list_length = 5; | 
|  | } | 
|  | buffer->AppendChar(' '); | 
|  | if (page_address < first_address) | 
|  | buffer->AppendChar('<'); | 
|  | buffer->AppendBase64(state.pfn, 4); | 
|  | pageframe_list_length += 5; | 
|  | if (pageframe_type_ == DUMP_PAGECOUNT && IsPageCountAvailable()) { | 
|  | uint64 pagecount = ReadPageCount(state.pfn); | 
|  | // Assume pagecount == 63 if the pageframe is mapped more than 63 times. | 
|  | if (pagecount > 63) | 
|  | pagecount = 63; | 
|  | buffer->AppendChar('#'); | 
|  | buffer->AppendBase64(pagecount, 1); | 
|  | pageframe_list_length += 2; | 
|  | } | 
|  | if (last_address < page_address - 1 + page_size) | 
|  | buffer->AppendChar('>'); | 
|  | // Begins a new line every 94 characters. | 
|  | if (pageframe_list_length > 94) { | 
|  | buffer->AppendChar('\n'); | 
|  | pageframe_list_length = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (state.is_committed) { | 
|  | // Calculate the size of the allocation part in this page. | 
|  | size_t bytes = page_size; | 
|  |  | 
|  | // If looking at the last page in a given region. | 
|  | if (last_address <= page_address - 1 + page_size) { | 
|  | bytes = last_address - page_address + 1; | 
|  | } | 
|  |  | 
|  | // If looking at the first page in a given region. | 
|  | if (page_address < first_address) { | 
|  | bytes -= first_address - page_address; | 
|  | } | 
|  |  | 
|  | committed_size += bytes; | 
|  | } | 
|  | if (page_address > MAX_ADDRESS - page_size) { | 
|  | break; | 
|  | } | 
|  | page_address += page_size; | 
|  | } | 
|  |  | 
|  | if (pageframe_type_ != DUMP_NO_PAGEFRAME && | 
|  | buffer != NULL && pageframe_list_length != 0) { | 
|  | buffer->AppendChar('\n'); | 
|  | } | 
|  |  | 
|  | return committed_size; | 
|  | } | 
|  |  | 
|  | uint64 DeepHeapProfile::MemoryInfoGetterLinux::ReadPageCount(uint64 pfn) const { | 
|  | int64 index = pfn * KPAGECOUNT_BYTES; | 
|  | int64 offset = lseek64(kpagecount_fd_, index, SEEK_SET); | 
|  | RAW_DCHECK(offset == index, "Failed in seeking in kpagecount."); | 
|  |  | 
|  | uint64 kpagecount_value; | 
|  | int result = read(kpagecount_fd_, &kpagecount_value, KPAGECOUNT_BYTES); | 
|  | if (result != KPAGECOUNT_BYTES) | 
|  | return 0; | 
|  |  | 
|  | return kpagecount_value; | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::MemoryInfoGetterLinux::Seek(uint64 address) const { | 
|  | int64 index = (address / getpagesize()) * PAGEMAP_BYTES; | 
|  | RAW_DCHECK(pagemap_fd_ != -1, "Failed to seek in /proc/self/pagemap"); | 
|  | int64 offset = lseek64(pagemap_fd_, index, SEEK_SET); | 
|  | RAW_DCHECK(offset == index, "Failed in seeking."); | 
|  | return offset >= 0; | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::MemoryInfoGetterLinux::Read( | 
|  | State* state, bool get_pfn) const { | 
|  | static const uint64 U64_1 = 1; | 
|  | static const uint64 PFN_FILTER = (U64_1 << 55) - U64_1; | 
|  | static const uint64 PAGE_PRESENT = U64_1 << 63; | 
|  | static const uint64 PAGE_SWAP = U64_1 << 62; | 
|  | static const uint64 PAGE_RESERVED = U64_1 << 61; | 
|  | static const uint64 FLAG_NOPAGE = U64_1 << 20; | 
|  | static const uint64 FLAG_KSM = U64_1 << 21; | 
|  | static const uint64 FLAG_MMAP = U64_1 << 11; | 
|  |  | 
|  | uint64 pagemap_value; | 
|  | RAW_DCHECK(pagemap_fd_ != -1, "Failed to read from /proc/self/pagemap"); | 
|  | int result = read(pagemap_fd_, &pagemap_value, PAGEMAP_BYTES); | 
|  | if (result != PAGEMAP_BYTES) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Check if the page is committed. | 
|  | state->is_committed = (pagemap_value & (PAGE_PRESENT | PAGE_SWAP)); | 
|  |  | 
|  | state->is_present = (pagemap_value & PAGE_PRESENT); | 
|  | state->is_swapped = (pagemap_value & PAGE_SWAP); | 
|  | state->is_shared = false; | 
|  |  | 
|  | if (get_pfn && state->is_present && !state->is_swapped) | 
|  | state->pfn = (pagemap_value & PFN_FILTER); | 
|  | else | 
|  | state->pfn = 0; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::MemoryInfoGetterLinux::IsPageCountAvailable() const { | 
|  | return kpagecount_fd_ != -1; | 
|  | } | 
|  |  | 
|  | #endif  // defined(__linux__) | 
|  |  | 
|  | DeepHeapProfile::MemoryResidenceInfoGetterInterface:: | 
|  | MemoryResidenceInfoGetterInterface() {} | 
|  |  | 
|  | DeepHeapProfile::MemoryResidenceInfoGetterInterface:: | 
|  | ~MemoryResidenceInfoGetterInterface() {} | 
|  |  | 
|  | DeepHeapProfile::MemoryResidenceInfoGetterInterface* | 
|  | DeepHeapProfile::MemoryResidenceInfoGetterInterface::Create( | 
|  | PageFrameType pageframe_type) { | 
|  | #if defined(_WIN32) || defined(_WIN64) | 
|  | return new MemoryInfoGetterWindows(pageframe_type); | 
|  | #elif defined(__linux__) | 
|  | return new MemoryInfoGetterLinux(pageframe_type); | 
|  | #else | 
|  | return NULL; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile, | 
|  | const char* prefix, | 
|  | enum PageFrameType pageframe_type) | 
|  | : memory_residence_info_getter_( | 
|  | MemoryResidenceInfoGetterInterface::Create(pageframe_type)), | 
|  | most_recent_pid_(-1), | 
|  | stats_(), | 
|  | dump_count_(0), | 
|  | filename_prefix_(NULL), | 
|  | deep_table_(kHashTableSize, heap_profile->alloc_, heap_profile->dealloc_), | 
|  | pageframe_type_(pageframe_type), | 
|  | heap_profile_(heap_profile) { | 
|  | // Copy filename prefix. | 
|  | const int prefix_length = strlen(prefix); | 
|  | filename_prefix_ = | 
|  | reinterpret_cast<char*>(heap_profile_->alloc_(prefix_length + 1)); | 
|  | memcpy(filename_prefix_, prefix, prefix_length); | 
|  | filename_prefix_[prefix_length] = '\0'; | 
|  |  | 
|  | strncpy(run_id_, "undetermined-run-id", sizeof(run_id_)); | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::~DeepHeapProfile() { | 
|  | heap_profile_->dealloc_(filename_prefix_); | 
|  | delete memory_residence_info_getter_; | 
|  | } | 
|  |  | 
|  | // Global malloc() should not be used in this function. | 
|  | // Use LowLevelAlloc if required. | 
|  | void DeepHeapProfile::DumpOrderedProfile(const char* reason, | 
|  | char raw_buffer[], | 
|  | int buffer_size, | 
|  | RawFD fd) { | 
|  | TextBuffer buffer(raw_buffer, buffer_size, fd); | 
|  |  | 
|  | #ifndef NDEBUG | 
|  | int64 starting_cycles = CycleClock::Now(); | 
|  | #endif | 
|  |  | 
|  | // Get the time before starting snapshot. | 
|  | // TODO(dmikurube): Consider gettimeofday if available. | 
|  | time_t time_value = time(NULL); | 
|  |  | 
|  | ++dump_count_; | 
|  |  | 
|  | // Re-open files in /proc/pid/ if the process is newly forked one. | 
|  | if (most_recent_pid_ != getpid()) { | 
|  | char hostname[64]; | 
|  | if (0 == gethostname(hostname, sizeof(hostname))) { | 
|  | char* dot = strchr(hostname, '.'); | 
|  | if (dot != NULL) | 
|  | *dot = '\0'; | 
|  | } else { | 
|  | strcpy(hostname, "unknown"); | 
|  | } | 
|  |  | 
|  | most_recent_pid_ = getpid(); | 
|  |  | 
|  | snprintf(run_id_, sizeof(run_id_), "%s-" OS_NAME "-%d-%lu", | 
|  | hostname, most_recent_pid_, time(NULL)); | 
|  |  | 
|  | if (memory_residence_info_getter_) | 
|  | memory_residence_info_getter_->Initialize(); | 
|  | deep_table_.ResetIsLogged(); | 
|  |  | 
|  | // Write maps into "|filename_prefix_|.<pid>.maps". | 
|  | WriteProcMaps(filename_prefix_, raw_buffer, buffer_size); | 
|  | } | 
|  |  | 
|  | // Reset committed sizes of buckets. | 
|  | deep_table_.ResetCommittedSize(); | 
|  |  | 
|  | // Record committed sizes. | 
|  | stats_.SnapshotAllocations(this); | 
|  |  | 
|  | // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. | 
|  | // glibc's snprintf internally allocates memory by alloca normally, but it | 
|  | // allocates memory by malloc if large memory is required. | 
|  |  | 
|  | buffer.AppendString(kProfileHeader, 0); | 
|  | buffer.AppendString(kProfileVersion, 0); | 
|  | buffer.AppendString("\n", 0); | 
|  |  | 
|  | // Fill buffer with meta information. | 
|  | buffer.AppendString(kMetaInformationHeader, 0); | 
|  |  | 
|  | buffer.AppendString("Time: ", 0); | 
|  | buffer.AppendUnsignedLong(time_value, 0); | 
|  | buffer.AppendChar('\n'); | 
|  |  | 
|  | if (reason != NULL) { | 
|  | buffer.AppendString("Reason: ", 0); | 
|  | buffer.AppendString(reason, 0); | 
|  | buffer.AppendChar('\n'); | 
|  | } | 
|  |  | 
|  | AppendCommandLine(&buffer); | 
|  |  | 
|  | buffer.AppendString("RunID: ", 0); | 
|  | buffer.AppendString(run_id_, 0); | 
|  | buffer.AppendChar('\n'); | 
|  |  | 
|  | buffer.AppendString("PageSize: ", 0); | 
|  | buffer.AppendInt(getpagesize(), 0, 0); | 
|  | buffer.AppendChar('\n'); | 
|  |  | 
|  | // Assumes the physical memory <= 64GB (PFN < 2^24). | 
|  | if (pageframe_type_ == DUMP_PAGECOUNT && memory_residence_info_getter_ && | 
|  | memory_residence_info_getter_->IsPageCountAvailable()) { | 
|  | buffer.AppendString("PageFrame: 24,Base64,PageCount", 0); | 
|  | buffer.AppendChar('\n'); | 
|  | } else if (pageframe_type_ != DUMP_NO_PAGEFRAME) { | 
|  | buffer.AppendString("PageFrame: 24,Base64", 0); | 
|  | buffer.AppendChar('\n'); | 
|  | } | 
|  |  | 
|  | // Fill buffer with the global stats. | 
|  | buffer.AppendString(kMMapListHeader, 0); | 
|  |  | 
|  | stats_.SnapshotMaps(memory_residence_info_getter_, this, &buffer); | 
|  |  | 
|  | // Fill buffer with the global stats. | 
|  | buffer.AppendString(kGlobalStatsHeader, 0); | 
|  |  | 
|  | stats_.Unparse(&buffer); | 
|  |  | 
|  | buffer.AppendString(kStacktraceHeader, 0); | 
|  | buffer.AppendString(kVirtualLabel, 10); | 
|  | buffer.AppendChar(' '); | 
|  | buffer.AppendString(kCommittedLabel, 10); | 
|  | buffer.AppendString("\n", 0); | 
|  |  | 
|  | // Fill buffer. | 
|  | deep_table_.UnparseForStats(&buffer); | 
|  |  | 
|  | buffer.Flush(); | 
|  |  | 
|  | // Write the bucket listing into a .bucket file. | 
|  | deep_table_.WriteForBucketFile( | 
|  | filename_prefix_, dump_count_, raw_buffer, buffer_size); | 
|  |  | 
|  | #ifndef NDEBUG | 
|  | int64 elapsed_cycles = CycleClock::Now() - starting_cycles; | 
|  | double elapsed_seconds = elapsed_cycles / CyclesPerSecond(); | 
|  | RAW_VLOG(0, "Time spent on DeepProfiler: %.3f sec\n", elapsed_seconds); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | int DeepHeapProfile::TextBuffer::Size() { | 
|  | return size_; | 
|  | } | 
|  |  | 
|  | int DeepHeapProfile::TextBuffer::FilledBytes() { | 
|  | return cursor_; | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::TextBuffer::Clear() { | 
|  | cursor_ = 0; | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::TextBuffer::Flush() { | 
|  | RawWrite(fd_, buffer_, cursor_); | 
|  | cursor_ = 0; | 
|  | } | 
|  |  | 
|  | // TODO(dmikurube): These Append* functions should not use snprintf. | 
|  | bool DeepHeapProfile::TextBuffer::AppendChar(char value) { | 
|  | return ForwardCursor(snprintf(buffer_ + cursor_, size_ - cursor_, | 
|  | "%c", value)); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendString(const char* value, int width) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%s", value); | 
|  | else | 
|  | appended = snprintf(position, available, "%*s", | 
|  | width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendInt(int value, int width, | 
|  | bool leading_zero) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%d", value); | 
|  | else if (leading_zero) | 
|  | appended = snprintf(position, available, "%0*d", width, value); | 
|  | else | 
|  | appended = snprintf(position, available, "%*d", width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendLong(long value, int width) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%ld", value); | 
|  | else | 
|  | appended = snprintf(position, available, "%*ld", width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendUnsignedLong(unsigned long value, | 
|  | int width) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%lu", value); | 
|  | else | 
|  | appended = snprintf(position, available, "%*lu", width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendInt64(int64 value, int width) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%" PRId64, value); | 
|  | else | 
|  | appended = snprintf(position, available, "%*" PRId64, width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendPtr(uint64 value, int width) { | 
|  | char* position = buffer_ + cursor_; | 
|  | int available = size_ - cursor_; | 
|  | int appended; | 
|  | if (width == 0) | 
|  | appended = snprintf(position, available, "%" PRIx64, value); | 
|  | else | 
|  | appended = snprintf(position, available, "%0*" PRIx64, width, value); | 
|  | return ForwardCursor(appended); | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::AppendBase64(uint64 value, int width) { | 
|  | static const char base64[65] = | 
|  | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | 
|  | #if defined(__BIG_ENDIAN__) | 
|  | value = bswap_64(value); | 
|  | #endif | 
|  | for (int shift = (width - 1) * 6; shift >= 0; shift -= 6) { | 
|  | if (!AppendChar(base64[(value >> shift) & 0x3f])) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool DeepHeapProfile::TextBuffer::ForwardCursor(int appended) { | 
|  | if (appended < 0 || appended >= size_ - cursor_) | 
|  | return false; | 
|  | cursor_ += appended; | 
|  | if (cursor_ > size_ * 4 / 5) | 
|  | Flush(); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DeepBucket::UnparseForStats(TextBuffer* buffer) { | 
|  | buffer->AppendInt64(bucket->alloc_size - bucket->free_size, 10); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendInt64(committed_size, 10); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendInt(bucket->allocs, 6, false); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendInt(bucket->frees, 6, false); | 
|  | buffer->AppendString(" @ ", 0); | 
|  | buffer->AppendInt(id, 0, false); | 
|  | buffer->AppendString("\n", 0); | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DeepBucket::UnparseForBucketFile(TextBuffer* buffer) { | 
|  | buffer->AppendInt(id, 0, false); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendString(is_mmap ? "mmap" : "malloc", 0); | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | buffer->AppendString(" t0x", 0); | 
|  | buffer->AppendPtr(reinterpret_cast<uintptr_t>(type), 0); | 
|  | if (type == NULL) { | 
|  | buffer->AppendString(" nno_typeinfo", 0); | 
|  | } else { | 
|  | buffer->AppendString(" n", 0); | 
|  | buffer->AppendString(type->name(), 0); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | for (int depth = 0; depth < bucket->depth; depth++) { | 
|  | buffer->AppendString(" 0x", 0); | 
|  | buffer->AppendPtr(reinterpret_cast<uintptr_t>(bucket->stack[depth]), 8); | 
|  | } | 
|  | buffer->AppendString("\n", 0); | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::DeepBucketTable::DeepBucketTable( | 
|  | int table_size, | 
|  | HeapProfileTable::Allocator alloc, | 
|  | HeapProfileTable::DeAllocator dealloc) | 
|  | : table_(NULL), | 
|  | table_size_(table_size), | 
|  | alloc_(alloc), | 
|  | dealloc_(dealloc), | 
|  | bucket_id_(0) { | 
|  | const int bytes = table_size * sizeof(DeepBucket*); | 
|  | table_ = reinterpret_cast<DeepBucket**>(alloc(bytes)); | 
|  | memset(table_, 0, bytes); | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::DeepBucketTable::~DeepBucketTable() { | 
|  | ASSERT(table_ != NULL); | 
|  | for (int db = 0; db < table_size_; db++) { | 
|  | for (DeepBucket* x = table_[db]; x != 0; /**/) { | 
|  | DeepBucket* db = x; | 
|  | x = x->next; | 
|  | dealloc_(db); | 
|  | } | 
|  | } | 
|  | dealloc_(table_); | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::DeepBucket* DeepHeapProfile::DeepBucketTable::Lookup( | 
|  | Bucket* bucket, | 
|  | #if defined(TYPE_PROFILING) | 
|  | const std::type_info* type, | 
|  | #endif | 
|  | bool is_mmap) { | 
|  | // Make hash-value | 
|  | uintptr_t h = 0; | 
|  |  | 
|  | AddToHashValue(reinterpret_cast<uintptr_t>(bucket), &h); | 
|  | if (is_mmap) { | 
|  | AddToHashValue(1, &h); | 
|  | } else { | 
|  | AddToHashValue(0, &h); | 
|  | } | 
|  |  | 
|  | #if defined(TYPE_PROFILING) | 
|  | if (type == NULL) { | 
|  | AddToHashValue(0, &h); | 
|  | } else { | 
|  | AddToHashValue(reinterpret_cast<uintptr_t>(type->name()), &h); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | FinishHashValue(&h); | 
|  |  | 
|  | // Lookup stack trace in table | 
|  | unsigned int buck = ((unsigned int) h) % table_size_; | 
|  | for (DeepBucket* db = table_[buck]; db != 0; db = db->next) { | 
|  | if (db->bucket == bucket) { | 
|  | return db; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Create a new bucket | 
|  | DeepBucket* db = reinterpret_cast<DeepBucket*>(alloc_(sizeof(DeepBucket))); | 
|  | memset(db, 0, sizeof(*db)); | 
|  | db->bucket         = bucket; | 
|  | #if defined(TYPE_PROFILING) | 
|  | db->type           = type; | 
|  | #endif | 
|  | db->committed_size = 0; | 
|  | db->is_mmap        = is_mmap; | 
|  | db->id             = (bucket_id_++); | 
|  | db->is_logged      = false; | 
|  | db->next           = table_[buck]; | 
|  | table_[buck] = db; | 
|  | return db; | 
|  | } | 
|  |  | 
|  | // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. | 
|  | void DeepHeapProfile::DeepBucketTable::UnparseForStats(TextBuffer* buffer) { | 
|  | for (int i = 0; i < table_size_; i++) { | 
|  | for (DeepBucket* deep_bucket = table_[i]; | 
|  | deep_bucket != NULL; | 
|  | deep_bucket = deep_bucket->next) { | 
|  | Bucket* bucket = deep_bucket->bucket; | 
|  | if (bucket->alloc_size - bucket->free_size == 0) { | 
|  | continue;  // Skip empty buckets. | 
|  | } | 
|  | deep_bucket->UnparseForStats(buffer); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DeepBucketTable::WriteForBucketFile( | 
|  | const char* prefix, int dump_count, char raw_buffer[], int buffer_size) { | 
|  | char filename[100]; | 
|  | snprintf(filename, sizeof(filename), | 
|  | "%s.%05d.%04d.buckets", prefix, getpid(), dump_count); | 
|  | RawFD fd = RawOpenForWriting(filename); | 
|  | RAW_DCHECK(fd != kIllegalRawFD, ""); | 
|  |  | 
|  | TextBuffer buffer(raw_buffer, buffer_size, fd); | 
|  |  | 
|  | for (int i = 0; i < table_size_; i++) { | 
|  | for (DeepBucket* deep_bucket = table_[i]; | 
|  | deep_bucket != NULL; | 
|  | deep_bucket = deep_bucket->next) { | 
|  | Bucket* bucket = deep_bucket->bucket; | 
|  | if (deep_bucket->is_logged) { | 
|  | continue;  // Skip the bucket if it is already logged. | 
|  | } | 
|  | if (!deep_bucket->is_mmap && | 
|  | bucket->alloc_size - bucket->free_size <= 64) { | 
|  | continue;  // Skip small malloc buckets. | 
|  | } | 
|  |  | 
|  | deep_bucket->UnparseForBucketFile(&buffer); | 
|  | deep_bucket->is_logged = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | buffer.Flush(); | 
|  | RawClose(fd); | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DeepBucketTable::ResetCommittedSize() { | 
|  | for (int i = 0; i < table_size_; i++) { | 
|  | for (DeepBucket* deep_bucket = table_[i]; | 
|  | deep_bucket != NULL; | 
|  | deep_bucket = deep_bucket->next) { | 
|  | deep_bucket->committed_size = 0; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DeepBucketTable::ResetIsLogged() { | 
|  | for (int i = 0; i < table_size_; i++) { | 
|  | for (DeepBucket* deep_bucket = table_[i]; | 
|  | deep_bucket != NULL; | 
|  | deep_bucket = deep_bucket->next) { | 
|  | deep_bucket->is_logged = false; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // This hash function is from HeapProfileTable::GetBucket. | 
|  | // static | 
|  | void DeepHeapProfile::DeepBucketTable::AddToHashValue( | 
|  | uintptr_t add, uintptr_t* hash_value) { | 
|  | *hash_value += add; | 
|  | *hash_value += *hash_value << 10; | 
|  | *hash_value ^= *hash_value >> 6; | 
|  | } | 
|  |  | 
|  | // This hash function is from HeapProfileTable::GetBucket. | 
|  | // static | 
|  | void DeepHeapProfile::DeepBucketTable::FinishHashValue(uintptr_t* hash_value) { | 
|  | *hash_value += *hash_value << 3; | 
|  | *hash_value ^= *hash_value >> 11; | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::RegionStats::Initialize() { | 
|  | virtual_bytes_ = 0; | 
|  | committed_bytes_ = 0; | 
|  | } | 
|  |  | 
|  | uint64 DeepHeapProfile::RegionStats::Record( | 
|  | const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, | 
|  | uint64 first_address, | 
|  | uint64 last_address, | 
|  | TextBuffer* buffer) { | 
|  | uint64 committed = 0; | 
|  | virtual_bytes_ += static_cast<size_t>(last_address - first_address + 1); | 
|  | if (memory_residence_info_getter) | 
|  | committed = memory_residence_info_getter->CommittedSize(first_address, | 
|  | last_address, | 
|  | buffer); | 
|  | committed_bytes_ += committed; | 
|  | return committed; | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::RegionStats::Unparse(const char* name, | 
|  | TextBuffer* buffer) { | 
|  | buffer->AppendString(name, 25); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendLong(virtual_bytes_, 12); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendLong(committed_bytes_, 12); | 
|  | buffer->AppendString("\n", 0); | 
|  | } | 
|  |  | 
|  | // Snapshots all virtual memory mapping stats by merging mmap(2) records from | 
|  | // MemoryRegionMap and /proc/maps, the OS-level memory mapping information. | 
|  | // Memory regions described in /proc/maps, but which are not created by mmap, | 
|  | // are accounted as "unhooked" memory regions. | 
|  | // | 
|  | // This function assumes that every memory region created by mmap is covered | 
|  | // by VMA(s) described in /proc/maps except for http://crbug.com/189114. | 
|  | // Note that memory regions created with mmap don't align with borders of VMAs | 
|  | // in /proc/maps.  In other words, a memory region by mmap can cut across many | 
|  | // VMAs.  Also, of course a VMA can include many memory regions by mmap. | 
|  | // It means that the following situation happens: | 
|  | // | 
|  | // => Virtual address | 
|  | // <----- VMA #1 -----><----- VMA #2 ----->...<----- VMA #3 -----><- VMA #4 -> | 
|  | // ..< mmap #1 >.<- mmap #2 -><- mmap #3 ->...<- mmap #4 ->..<-- mmap #5 -->.. | 
|  | // | 
|  | // It can happen easily as permission can be changed by mprotect(2) for a part | 
|  | // of a memory region.  A change in permission splits VMA(s). | 
|  | // | 
|  | // To deal with the situation, this function iterates over MemoryRegionMap and | 
|  | // /proc/maps independently.  The iterator for MemoryRegionMap is initialized | 
|  | // at the top outside the loop for /proc/maps, and it goes forward inside the | 
|  | // loop while comparing their addresses. | 
|  | // | 
|  | // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. | 
|  | void DeepHeapProfile::GlobalStats::SnapshotMaps( | 
|  | const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, | 
|  | DeepHeapProfile* deep_profile, | 
|  | TextBuffer* mmap_dump_buffer) { | 
|  | MemoryRegionMap::LockHolder lock_holder; | 
|  | ProcMapsIterator::Buffer procmaps_iter_buffer; | 
|  | ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer); | 
|  | uint64 vma_start_addr, vma_last_addr, offset; | 
|  | int64 inode; | 
|  | char* flags; | 
|  | char* filename; | 
|  | enum MapsRegionType type; | 
|  |  | 
|  | for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { | 
|  | all_[i].Initialize(); | 
|  | unhooked_[i].Initialize(); | 
|  | } | 
|  | profiled_mmap_.Initialize(); | 
|  |  | 
|  | MemoryRegionMap::RegionIterator mmap_iter = | 
|  | MemoryRegionMap::BeginRegionLocked(); | 
|  | DeepBucket* deep_bucket = NULL; | 
|  | if (mmap_iter != MemoryRegionMap::EndRegionLocked()) { | 
|  | deep_bucket = GetInformationOfMemoryRegion( | 
|  | mmap_iter, memory_residence_info_getter, deep_profile); | 
|  | } | 
|  |  | 
|  | while (procmaps_iter.Next(&vma_start_addr, &vma_last_addr, | 
|  | &flags, &offset, &inode, &filename)) { | 
|  | if (mmap_dump_buffer) { | 
|  | char buffer[1024]; | 
|  | int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), | 
|  | vma_start_addr, vma_last_addr, | 
|  | flags, offset, inode, filename, 0); | 
|  | mmap_dump_buffer->AppendString(buffer, 0); | 
|  | } | 
|  |  | 
|  | // 'vma_last_addr' should be the last inclusive address of the region. | 
|  | vma_last_addr -= 1; | 
|  | if (strcmp("[vsyscall]", filename) == 0) { | 
|  | continue;  // Reading pagemap will fail in [vsyscall]. | 
|  | } | 
|  |  | 
|  | // TODO(dmikurube): |type| will be deprecated in the dump. | 
|  | // See http://crbug.com/245603. | 
|  | type = ABSENT; | 
|  | if (filename[0] == '/') { | 
|  | if (flags[2] == 'x') | 
|  | type = FILE_EXEC; | 
|  | else | 
|  | type = FILE_NONEXEC; | 
|  | } else if (filename[0] == '\0' || filename[0] == '\n') { | 
|  | type = ANONYMOUS; | 
|  | } else if (strcmp(filename, "[stack]") == 0) { | 
|  | type = STACK; | 
|  | } else { | 
|  | type = OTHER; | 
|  | } | 
|  | // TODO(dmikurube): This |all_| count should be removed in future soon. | 
|  | // See http://crbug.com/245603. | 
|  | uint64 vma_total = all_[type].Record( | 
|  | memory_residence_info_getter, vma_start_addr, vma_last_addr, NULL); | 
|  | uint64 vma_subtotal = 0; | 
|  |  | 
|  | // TODO(dmikurube): Stop double-counting pagemap. | 
|  | // It will be fixed when http://crbug.com/245603 finishes. | 
|  | if (MemoryRegionMap::IsRecordingLocked()) { | 
|  | uint64 cursor = vma_start_addr; | 
|  | bool first = true; | 
|  |  | 
|  | // Iterates over MemoryRegionMap until the iterator moves out of the VMA. | 
|  | do { | 
|  | if (!first) { | 
|  | cursor = mmap_iter->end_addr; | 
|  | ++mmap_iter; | 
|  | // Don't break here even if mmap_iter == EndRegionLocked(). | 
|  |  | 
|  | if (mmap_iter != MemoryRegionMap::EndRegionLocked()) { | 
|  | deep_bucket = GetInformationOfMemoryRegion( | 
|  | mmap_iter, memory_residence_info_getter, deep_profile); | 
|  | } | 
|  | } | 
|  | first = false; | 
|  |  | 
|  | uint64 last_address_of_unhooked; | 
|  | // If the next mmap entry is away from the current VMA. | 
|  | if (mmap_iter == MemoryRegionMap::EndRegionLocked() || | 
|  | mmap_iter->start_addr > vma_last_addr) { | 
|  | last_address_of_unhooked = vma_last_addr; | 
|  | } else { | 
|  | last_address_of_unhooked = mmap_iter->start_addr - 1; | 
|  | } | 
|  |  | 
|  | if (last_address_of_unhooked + 1 > cursor) { | 
|  | RAW_CHECK(cursor >= vma_start_addr, | 
|  | "Wrong calculation for unhooked"); | 
|  | RAW_CHECK(last_address_of_unhooked <= vma_last_addr, | 
|  | "Wrong calculation for unhooked"); | 
|  | uint64 committed_size = unhooked_[type].Record( | 
|  | memory_residence_info_getter, | 
|  | cursor, | 
|  | last_address_of_unhooked, | 
|  | mmap_dump_buffer); | 
|  | vma_subtotal += committed_size; | 
|  | if (mmap_dump_buffer) { | 
|  | mmap_dump_buffer->AppendString("  ", 0); | 
|  | mmap_dump_buffer->AppendPtr(cursor, 0); | 
|  | mmap_dump_buffer->AppendString(" - ", 0); | 
|  | mmap_dump_buffer->AppendPtr(last_address_of_unhooked + 1, 0); | 
|  | mmap_dump_buffer->AppendString("  unhooked ", 0); | 
|  | mmap_dump_buffer->AppendInt64(committed_size, 0); | 
|  | mmap_dump_buffer->AppendString(" / ", 0); | 
|  | mmap_dump_buffer->AppendInt64( | 
|  | last_address_of_unhooked - cursor + 1, 0); | 
|  | mmap_dump_buffer->AppendString("\n", 0); | 
|  | } | 
|  | cursor = last_address_of_unhooked + 1; | 
|  | } | 
|  |  | 
|  | if (mmap_iter != MemoryRegionMap::EndRegionLocked() && | 
|  | mmap_iter->start_addr <= vma_last_addr && | 
|  | mmap_dump_buffer) { | 
|  | bool trailing = mmap_iter->start_addr < vma_start_addr; | 
|  | bool continued = mmap_iter->end_addr - 1 > vma_last_addr; | 
|  | uint64 partial_first_address, partial_last_address; | 
|  | if (trailing) | 
|  | partial_first_address = vma_start_addr; | 
|  | else | 
|  | partial_first_address = mmap_iter->start_addr; | 
|  | if (continued) | 
|  | partial_last_address = vma_last_addr; | 
|  | else | 
|  | partial_last_address = mmap_iter->end_addr - 1; | 
|  | uint64 committed_size = 0; | 
|  | if (memory_residence_info_getter) | 
|  | committed_size = memory_residence_info_getter->CommittedSize( | 
|  | partial_first_address, partial_last_address, mmap_dump_buffer); | 
|  | vma_subtotal += committed_size; | 
|  | mmap_dump_buffer->AppendString(trailing ? " (" : "  ", 0); | 
|  | mmap_dump_buffer->AppendPtr(mmap_iter->start_addr, 0); | 
|  | mmap_dump_buffer->AppendString(trailing ? ")" : " ", 0); | 
|  | mmap_dump_buffer->AppendString("-", 0); | 
|  | mmap_dump_buffer->AppendString(continued ? "(" : " ", 0); | 
|  | mmap_dump_buffer->AppendPtr(mmap_iter->end_addr, 0); | 
|  | mmap_dump_buffer->AppendString(continued ? ")" : " ", 0); | 
|  | mmap_dump_buffer->AppendString(" hooked ", 0); | 
|  | mmap_dump_buffer->AppendInt64(committed_size, 0); | 
|  | mmap_dump_buffer->AppendString(" / ", 0); | 
|  | mmap_dump_buffer->AppendInt64( | 
|  | partial_last_address - partial_first_address + 1, 0); | 
|  | mmap_dump_buffer->AppendString(" @ ", 0); | 
|  | if (deep_bucket != NULL) { | 
|  | mmap_dump_buffer->AppendInt(deep_bucket->id, 0, false); | 
|  | } else { | 
|  | mmap_dump_buffer->AppendInt(0, 0, false); | 
|  | } | 
|  | mmap_dump_buffer->AppendString("\n", 0); | 
|  | } | 
|  | } while (mmap_iter != MemoryRegionMap::EndRegionLocked() && | 
|  | mmap_iter->end_addr - 1 <= vma_last_addr); | 
|  | } | 
|  |  | 
|  | if (vma_total != vma_subtotal) { | 
|  | char buffer[1024]; | 
|  | int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), | 
|  | vma_start_addr, vma_last_addr, | 
|  | flags, offset, inode, filename, 0); | 
|  | RAW_VLOG(0, "[%d] Mismatched total in VMA %" PRId64 ":" | 
|  | "%" PRId64 " (%" PRId64 ")", | 
|  | getpid(), vma_total, vma_subtotal, vma_total - vma_subtotal); | 
|  | RAW_VLOG(0, "[%d]   in %s", getpid(), buffer); | 
|  | } | 
|  | } | 
|  |  | 
|  | // TODO(dmikurube): Investigate and fix http://crbug.com/189114. | 
|  | // | 
|  | // The total committed memory usage in all_ (from /proc/<pid>/maps) is | 
|  | // sometimes smaller than the sum of the committed mmap'ed addresses and | 
|  | // unhooked regions.  Within our observation, the difference was only 4KB | 
|  | // in committed usage, zero in reserved virtual addresses | 
|  | // | 
|  | // A guess is that an uncommitted (but reserved) page may become committed | 
|  | // during counting memory usage in the loop above. | 
|  | // | 
|  | // The difference is accounted as "ABSENT" to investigate such cases. | 
|  | // | 
|  | // It will be fixed when http://crbug.com/245603 finishes (no double count). | 
|  |  | 
|  | RegionStats all_total; | 
|  | RegionStats unhooked_total; | 
|  | for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { | 
|  | all_total.AddAnotherRegionStat(all_[i]); | 
|  | unhooked_total.AddAnotherRegionStat(unhooked_[i]); | 
|  | } | 
|  |  | 
|  | size_t absent_virtual = profiled_mmap_.virtual_bytes() + | 
|  | unhooked_total.virtual_bytes() - | 
|  | all_total.virtual_bytes(); | 
|  | if (absent_virtual > 0) | 
|  | all_[ABSENT].AddToVirtualBytes(absent_virtual); | 
|  |  | 
|  | size_t absent_committed = profiled_mmap_.committed_bytes() + | 
|  | unhooked_total.committed_bytes() - | 
|  | all_total.committed_bytes(); | 
|  | if (absent_committed > 0) | 
|  | all_[ABSENT].AddToCommittedBytes(absent_committed); | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::GlobalStats::SnapshotAllocations( | 
|  | DeepHeapProfile* deep_profile) { | 
|  | profiled_malloc_.Initialize(); | 
|  |  | 
|  | deep_profile->heap_profile_->address_map_->Iterate(RecordAlloc, deep_profile); | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::GlobalStats::Unparse(TextBuffer* buffer) { | 
|  | RegionStats all_total; | 
|  | RegionStats unhooked_total; | 
|  | for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { | 
|  | all_total.AddAnotherRegionStat(all_[i]); | 
|  | unhooked_total.AddAnotherRegionStat(unhooked_[i]); | 
|  | } | 
|  |  | 
|  | // "# total (%lu) %c= profiled-mmap (%lu) + nonprofiled-* (%lu)\n" | 
|  | buffer->AppendString("# total (", 0); | 
|  | buffer->AppendUnsignedLong(all_total.committed_bytes(), 0); | 
|  | buffer->AppendString(") ", 0); | 
|  | buffer->AppendChar(all_total.committed_bytes() == | 
|  | profiled_mmap_.committed_bytes() + | 
|  | unhooked_total.committed_bytes() ? '=' : '!'); | 
|  | buffer->AppendString("= profiled-mmap (", 0); | 
|  | buffer->AppendUnsignedLong(profiled_mmap_.committed_bytes(), 0); | 
|  | buffer->AppendString(") + nonprofiled-* (", 0); | 
|  | buffer->AppendUnsignedLong(unhooked_total.committed_bytes(), 0); | 
|  | buffer->AppendString(")\n", 0); | 
|  |  | 
|  | // "                               virtual    committed" | 
|  | buffer->AppendString("", 26); | 
|  | buffer->AppendString(kVirtualLabel, 12); | 
|  | buffer->AppendChar(' '); | 
|  | buffer->AppendString(kCommittedLabel, 12); | 
|  | buffer->AppendString("\n", 0); | 
|  |  | 
|  | all_total.Unparse("total", buffer); | 
|  | all_[ABSENT].Unparse("absent", buffer); | 
|  | all_[FILE_EXEC].Unparse("file-exec", buffer); | 
|  | all_[FILE_NONEXEC].Unparse("file-nonexec", buffer); | 
|  | all_[ANONYMOUS].Unparse("anonymous", buffer); | 
|  | all_[STACK].Unparse("stack", buffer); | 
|  | all_[OTHER].Unparse("other", buffer); | 
|  | unhooked_total.Unparse("nonprofiled-total", buffer); | 
|  | unhooked_[ABSENT].Unparse("nonprofiled-absent", buffer); | 
|  | unhooked_[ANONYMOUS].Unparse("nonprofiled-anonymous", buffer); | 
|  | unhooked_[FILE_EXEC].Unparse("nonprofiled-file-exec", buffer); | 
|  | unhooked_[FILE_NONEXEC].Unparse("nonprofiled-file-nonexec", buffer); | 
|  | unhooked_[STACK].Unparse("nonprofiled-stack", buffer); | 
|  | unhooked_[OTHER].Unparse("nonprofiled-other", buffer); | 
|  | profiled_mmap_.Unparse("profiled-mmap", buffer); | 
|  | profiled_malloc_.Unparse("profiled-malloc", buffer); | 
|  | } | 
|  |  | 
|  | // static | 
|  | void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer, | 
|  | AllocValue* alloc_value, | 
|  | DeepHeapProfile* deep_profile) { | 
|  | uint64 address = reinterpret_cast<uintptr_t>(pointer); | 
|  | size_t committed = deep_profile->memory_residence_info_getter_->CommittedSize( | 
|  | address, address + alloc_value->bytes - 1, NULL); | 
|  |  | 
|  | DeepBucket* deep_bucket = deep_profile->deep_table_.Lookup( | 
|  | alloc_value->bucket(), | 
|  | #if defined(TYPE_PROFILING) | 
|  | LookupType(pointer), | 
|  | #endif | 
|  | /* is_mmap */ false); | 
|  | deep_bucket->committed_size += committed; | 
|  | deep_profile->stats_.profiled_malloc_.AddToVirtualBytes(alloc_value->bytes); | 
|  | deep_profile->stats_.profiled_malloc_.AddToCommittedBytes(committed); | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::DeepBucket* | 
|  | DeepHeapProfile::GlobalStats::GetInformationOfMemoryRegion( | 
|  | const MemoryRegionMap::RegionIterator& mmap_iter, | 
|  | const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, | 
|  | DeepHeapProfile* deep_profile) { | 
|  | size_t committed = deep_profile->memory_residence_info_getter_-> | 
|  | CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1, NULL); | 
|  |  | 
|  | // TODO(dmikurube): Store a reference to the bucket in region. | 
|  | Bucket* bucket = MemoryRegionMap::GetBucket( | 
|  | mmap_iter->call_stack_depth, mmap_iter->call_stack); | 
|  | DeepBucket* deep_bucket = NULL; | 
|  | if (bucket != NULL) { | 
|  | deep_bucket = deep_profile->deep_table_.Lookup( | 
|  | bucket, | 
|  | #if defined(TYPE_PROFILING) | 
|  | NULL,  // No type information for memory regions by mmap. | 
|  | #endif | 
|  | /* is_mmap */ true); | 
|  | if (deep_bucket != NULL) | 
|  | deep_bucket->committed_size += committed; | 
|  | } | 
|  |  | 
|  | profiled_mmap_.AddToVirtualBytes( | 
|  | mmap_iter->end_addr - mmap_iter->start_addr); | 
|  | profiled_mmap_.AddToCommittedBytes(committed); | 
|  |  | 
|  | return deep_bucket; | 
|  | } | 
|  |  | 
|  | // static | 
|  | void DeepHeapProfile::WriteProcMaps(const char* prefix, | 
|  | char raw_buffer[], | 
|  | int buffer_size) { | 
|  | char filename[100]; | 
|  | snprintf(filename, sizeof(filename), | 
|  | "%s.%05d.maps", prefix, static_cast<int>(getpid())); | 
|  |  | 
|  | RawFD fd = RawOpenForWriting(filename); | 
|  | RAW_DCHECK(fd != kIllegalRawFD, ""); | 
|  |  | 
|  | int length; | 
|  | bool wrote_all; | 
|  | length = tcmalloc::FillProcSelfMaps(raw_buffer, buffer_size, &wrote_all); | 
|  | RAW_DCHECK(wrote_all, ""); | 
|  | RAW_DCHECK(length <= buffer_size, ""); | 
|  | RawWrite(fd, raw_buffer, length); | 
|  | RawClose(fd); | 
|  | } | 
|  | #else  // USE_DEEP_HEAP_PROFILE | 
|  |  | 
|  | DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile, | 
|  | const char* prefix, | 
|  | enum PageFrameType pageframe_type) | 
|  | : heap_profile_(heap_profile) { | 
|  | } | 
|  |  | 
|  | DeepHeapProfile::~DeepHeapProfile() { | 
|  | } | 
|  |  | 
|  | void DeepHeapProfile::DumpOrderedProfile(const char* reason, | 
|  | char raw_buffer[], | 
|  | int buffer_size, | 
|  | RawFD fd) { | 
|  | } | 
|  |  | 
|  | #endif  // USE_DEEP_HEAP_PROFILE |