diff options
author | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-14 20:34:12 +0000 |
---|---|---|
committer | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-14 20:34:12 +0000 |
commit | d7ee903ba71a0f7ce3ddcd416130f8e25b65aef9 (patch) | |
tree | f0dc080abcbbf3a5df74382d42d8568d2490920a /third_party/tcmalloc | |
parent | cf562b77683aeb8458aa3040a45f35475b59b763 (diff) | |
download | chromium_src-d7ee903ba71a0f7ce3ddcd416130f8e25b65aef9.zip chromium_src-d7ee903ba71a0f7ce3ddcd416130f8e25b65aef9.tar.gz chromium_src-d7ee903ba71a0f7ce3ddcd416130f8e25b65aef9.tar.bz2 |
Count m(un)map for each stacktrace in MemoryRegionMap instead of HeapProfileTable.
This patch fixes a bug that gperftools(TCMalloc)'s mmap profiler
(HEAP_PROFILE_MMAP) doesn't hook some memory pages used by the
profiler itself.
This problem has been lived in gperftools for a long time.
It is discussed in gperftools' issue 502.
https://code.google.com/p/gperftools/issues/detail?id=502
Some bugs in the mmap profiler were fixed by
https://code.google.com/p/gperftools/issues/detail?id=383,
but the patch in the issue 383 didn't fix the bug mentioned in
the issue 502.
This change reverts the previous patch and http://crrev.com/132771
at first. Then, it modifies MemoryRegionMap to count m(un)map
calls for each stacktrace in itself instead of merging the counts
for each stacktrace in HeapProfileTable.
This change also cleans up heap-profiler, heap-profile-table and
deep-heap-profile.
This change is to be upstreamed to the original gperftools against
the issue 502.
BUG=181517
Review URL: https://chromiumcodereview.appspot.com/12388070
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@188176 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/tcmalloc')
-rw-r--r-- | third_party/tcmalloc/chromium/src/deep-heap-profile.cc | 187 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/deep-heap-profile.h | 26 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-checker.cc | 2 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-profile-stats.h | 52 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-profile-table.cc | 230 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-profile-table.h | 136 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/heap-profiler.cc | 59 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/memory_region_map.cc | 166 | ||||
-rw-r--r-- | third_party/tcmalloc/chromium/src/memory_region_map.h | 76 |
9 files changed, 499 insertions, 435 deletions
diff --git a/third_party/tcmalloc/chromium/src/deep-heap-profile.cc b/third_party/tcmalloc/chromium/src/deep-heap-profile.cc index 8027073..57d157e 100644 --- a/third_party/tcmalloc/chromium/src/deep-heap-profile.cc +++ b/third_party/tcmalloc/chromium/src/deep-heap-profile.cc @@ -21,6 +21,7 @@ #include "base/cycleclock.h" #include "base/sysinfo.h" #include "internal_logging.h" // for ASSERT, etc +#include "memory_region_map.h" static const int kProfilerBufferSize = 1 << 20; static const int kHashTableSize = 179999; // Same as heap-profile-table.cc. @@ -110,6 +111,7 @@ size_t MemoryInfoGetterLinux::CommittedSize( // Read corresponding physical page. State state; // TODO(dmikurube): Read pagemap in bulk for speed. + // TODO(dmikurube): Consider using mincore(2). if (Read(&state) == false) { // We can't read the last region (e.g vsyscall). #ifndef NDEBUG @@ -249,33 +251,13 @@ int DeepHeapProfile::FillOrderedProfile(char raw_buffer[], int buffer_size) { // Reset committed sizes of buckets. deep_table_.ResetCommittedSize(); - // Allocate a list for mmap'ed regions. - num_mmap_allocations_ = 0; - if (heap_profile_->mmap_address_map_) { - heap_profile_->mmap_address_map_->Iterate(CountMMap, this); - - mmap_list_length_ = 0; - mmap_list_ = reinterpret_cast<MMapListEntry*>(heap_profile_->alloc_( - sizeof(MMapListEntry) * num_mmap_allocations_)); - - // Touch all the allocated pages. Touching is required to avoid new page - // commitment while filling the list in SnapshotProcMaps. - for (int i = 0; - i < num_mmap_allocations_; - i += getpagesize() / 2 / sizeof(MMapListEntry)) - mmap_list_[i].first_address = 0; - mmap_list_[num_mmap_allocations_ - 1].last_address = 0; - } - - stats_.SnapshotProcMaps(memory_residence_info_getter_, NULL, 0, NULL); + // 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. - // Record committed sizes. - stats_.SnapshotAllocations(this); - buffer.AppendString(kProfileHeader, 0); buffer.AppendString(kProfileVersion, 0); buffer.AppendString("\n", 0); @@ -283,11 +265,7 @@ int DeepHeapProfile::FillOrderedProfile(char raw_buffer[], int buffer_size) { // Fill buffer with the global stats. buffer.AppendString(kMMapListHeader, 0); - // Check if committed bytes changed during SnapshotAllocations. - stats_.SnapshotProcMaps(memory_residence_info_getter_, - mmap_list_, - mmap_list_length_, - &buffer); + stats_.SnapshotMaps(memory_residence_info_getter_, this, &buffer); // Fill buffer with the global stats. buffer.AppendString(kGlobalStatsHeader, 0); @@ -305,9 +283,6 @@ int DeepHeapProfile::FillOrderedProfile(char raw_buffer[], int buffer_size) { RAW_DCHECK(buffer.FilledBytes() < buffer_size, ""); - heap_profile_->dealloc_(mmap_list_); - mmap_list_ = NULL; - // Write the bucket listing into a .bucket file. deep_table_.WriteForBucketFile(filename_prefix_, dump_count_, &global_buffer); @@ -628,32 +603,34 @@ void DeepHeapProfile::RegionStats::Unparse(const char* name, } // TODO(dmikurube): Eliminate dynamic memory allocation caused by snprintf. -void DeepHeapProfile::GlobalStats::SnapshotProcMaps( +void DeepHeapProfile::GlobalStats::SnapshotMaps( const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, - MMapListEntry* mmap_list, - int mmap_list_length, + DeepHeapProfile* deep_profile, TextBuffer* mmap_dump_buffer) { - ProcMapsIterator::Buffer iterator_buffer; - ProcMapsIterator iterator(0, &iterator_buffer); + MemoryRegionMap::LockHolder lock_holder; + ProcMapsIterator::Buffer procmaps_iter_buffer; + ProcMapsIterator procmaps_iter(0, &procmaps_iter_buffer); uint64 first_address, last_address, offset; int64 inode; char* flags; char* filename; - int mmap_list_index = 0; enum MapsRegionType type; - for (int i = 0; i < NUMBER_OF_MAPS_REGION_TYPES; ++i) { all_[i].Initialize(); unhooked_[i].Initialize(); } + profiled_mmap_.Initialize(); - while (iterator.Next(&first_address, &last_address, - &flags, &offset, &inode, &filename)) { + MemoryRegionMap::RegionIterator mmap_iter = + MemoryRegionMap::BeginRegionLocked(); + + while (procmaps_iter.Next(&first_address, &last_address, + &flags, &offset, &inode, &filename)) { if (mmap_dump_buffer) { char buffer[1024]; - int written = iterator.FormatLine(buffer, sizeof(buffer), - first_address, last_address, flags, - offset, inode, filename, 0); + int written = procmaps_iter.FormatLine(buffer, sizeof(buffer), + first_address, last_address, flags, + offset, inode, filename, 0); mmap_dump_buffer->AppendString(buffer, 0); } @@ -681,27 +658,49 @@ void DeepHeapProfile::GlobalStats::SnapshotProcMaps( // TODO(dmikurube): Stop double-counting pagemap. // Counts unhooked memory regions in /proc/<pid>/maps. - if (mmap_list != NULL) { + if (MemoryRegionMap::IsRecordingLocked()) { // It assumes that every mmap'ed region is included in one maps line. uint64 cursor = first_address; bool first = true; do { + Bucket* bucket = NULL; + DeepBucket* deep_bucket = NULL; if (!first) { - mmap_list[mmap_list_index].type = type; - cursor = mmap_list[mmap_list_index].last_address + 1; - ++mmap_list_index; + size_t committed = deep_profile->memory_residence_info_getter_-> + CommittedSize(mmap_iter->start_addr, mmap_iter->end_addr - 1); + // 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 mmap'ed memory regions. +#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); + + cursor = mmap_iter->end_addr; + ++mmap_iter; + // Don't break here even if mmap_iter == EndRegionLocked(). } first = false; uint64 last_address_of_unhooked; // If the next mmap entry is away from the current maps line. - if (mmap_list_index >= mmap_list_length || - mmap_list[mmap_list_index].first_address > last_address) { + if (mmap_iter == MemoryRegionMap::EndRegionLocked() || + mmap_iter->start_addr > last_address) { last_address_of_unhooked = last_address; } else { - last_address_of_unhooked = - mmap_list[mmap_list_index].first_address - 1; + last_address_of_unhooked = mmap_iter->start_addr - 1; } if (last_address_of_unhooked + 1 > cursor) { @@ -723,52 +722,39 @@ void DeepHeapProfile::GlobalStats::SnapshotProcMaps( cursor = last_address_of_unhooked + 1; } - if (mmap_list_index < mmap_list_length && - mmap_list[mmap_list_index].first_address <= last_address && + if (mmap_iter != MemoryRegionMap::EndRegionLocked() && + mmap_iter->start_addr <= last_address && mmap_dump_buffer) { - bool trailing = - mmap_list[mmap_list_index].first_address < first_address; - bool continued = - mmap_list[mmap_list_index].last_address > last_address; + bool trailing = mmap_iter->start_addr < first_address; + bool continued = mmap_iter->end_addr - 1 > last_address; mmap_dump_buffer->AppendString(trailing ? " (" : " ", 0); - mmap_dump_buffer->AppendPtr( - mmap_list[mmap_list_index].first_address, 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_list[mmap_list_index].last_address + 1, 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->AppendString(kMapsRegionTypeDict[type], 0); mmap_dump_buffer->AppendString(" @ ", 0); - mmap_dump_buffer->AppendInt( - mmap_list[mmap_list_index].deep_bucket->id, 0); + if (deep_bucket != NULL) { + mmap_dump_buffer->AppendInt(deep_bucket->id, 0); + } else { + mmap_dump_buffer->AppendInt(0, 0); + } mmap_dump_buffer->AppendString("\n", 0); } - } while (mmap_list_index < mmap_list_length && - mmap_list[mmap_list_index].last_address <= last_address); + } while (mmap_iter != MemoryRegionMap::EndRegionLocked() && + mmap_iter->end_addr - 1 <= last_address); } } } void DeepHeapProfile::GlobalStats::SnapshotAllocations( DeepHeapProfile* deep_profile) { - profiled_mmap_.Initialize(); profiled_malloc_.Initialize(); - // malloc allocations. - deep_profile->heap_profile_->alloc_address_map_->Iterate(RecordAlloc, - deep_profile); - - // mmap allocations. - if (deep_profile->heap_profile_->mmap_address_map_) { - deep_profile->heap_profile_->mmap_address_map_->Iterate(RecordMMap, - deep_profile); - std::sort(deep_profile->mmap_list_, - deep_profile->mmap_list_ + deep_profile->mmap_list_length_, - ByFirstAddress); - } + deep_profile->heap_profile_->address_map_->Iterate(RecordAlloc, deep_profile); } void DeepHeapProfile::GlobalStats::Unparse(TextBuffer* buffer) { @@ -817,12 +803,6 @@ void DeepHeapProfile::GlobalStats::Unparse(TextBuffer* buffer) { } // static -bool DeepHeapProfile::GlobalStats::ByFirstAddress(const MMapListEntry& a, - const MMapListEntry& b) { - return a.first_address < b.first_address; -} - -// static void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer, AllocValue* alloc_value, DeepHeapProfile* deep_profile) { @@ -842,40 +822,6 @@ void DeepHeapProfile::GlobalStats::RecordAlloc(const void* pointer, } // static -void DeepHeapProfile::GlobalStats::RecordMMap(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); - - DeepBucket* deep_bucket = deep_profile->deep_table_.Lookup( - alloc_value->bucket(), -#if defined(TYPE_PROFILING) - NULL, -#endif - /* is_mmap */ true); - deep_bucket->committed_size += committed; - deep_profile->stats_.profiled_mmap_.AddToVirtualBytes(alloc_value->bytes); - deep_profile->stats_.profiled_mmap_.AddToCommittedBytes(committed); - - if (deep_profile->mmap_list_length_ < deep_profile->num_mmap_allocations_) { - deep_profile->mmap_list_[deep_profile->mmap_list_length_].first_address = - address; - deep_profile->mmap_list_[deep_profile->mmap_list_length_].last_address = - address - 1 + alloc_value->bytes; - deep_profile->mmap_list_[deep_profile->mmap_list_length_].type = ABSENT; - deep_profile->mmap_list_[deep_profile->mmap_list_length_].deep_bucket = - deep_bucket; - ++deep_profile->mmap_list_length_; - } else { - RAW_LOG(0, "Unexpected number of mmap entries: %d/%d", - deep_profile->mmap_list_length_, - deep_profile->num_mmap_allocations_); - } -} - -// static void DeepHeapProfile::WriteProcMaps(const char* prefix, int buffer_size, char raw_buffer[]) { @@ -894,13 +840,6 @@ void DeepHeapProfile::WriteProcMaps(const char* prefix, RawWrite(fd, raw_buffer, length); RawClose(fd); } - -// static -void DeepHeapProfile::CountMMap(const void* pointer, - AllocValue* alloc_value, - DeepHeapProfile* deep_profile) { - ++deep_profile->num_mmap_allocations_; -} #else // DEEP_HEAP_PROFILE DeepHeapProfile::DeepHeapProfile(HeapProfileTable* heap_profile, diff --git a/third_party/tcmalloc/chromium/src/deep-heap-profile.h b/third_party/tcmalloc/chromium/src/deep-heap-profile.h index d68fa1c..f820761 100644 --- a/third_party/tcmalloc/chromium/src/deep-heap-profile.h +++ b/third_party/tcmalloc/chromium/src/deep-heap-profile.h @@ -211,13 +211,6 @@ class DeepHeapProfile { int bucket_id_; }; - struct MMapListEntry { - uint64 first_address; - uint64 last_address; - MapsRegionType type; - DeepBucket* deep_bucket; - }; - class RegionStats { public: RegionStats(): virtual_bytes_(0), committed_bytes_(0) {} @@ -258,10 +251,9 @@ class DeepHeapProfile { class GlobalStats { public: // Snapshots and calculates global stats from /proc/<pid>/maps and pagemap. - void SnapshotProcMaps( + void SnapshotMaps( const MemoryResidenceInfoGetterInterface* memory_residence_info_getter, - MMapListEntry* mmap_list, - int mmap_list_length, + DeepHeapProfile* deep_profile, TextBuffer* mmap_dump_buffer); // Snapshots allocations by malloc and mmap. @@ -271,17 +263,11 @@ class DeepHeapProfile { void Unparse(TextBuffer* buffer); private: - static bool ByFirstAddress(const MMapListEntry& a, - const MMapListEntry& b); - // Records both virtual and committed byte counts of malloc and mmap regions // as callback functions for AllocationMap::Iterate(). static void RecordAlloc(const void* pointer, AllocValue* alloc_value, DeepHeapProfile* deep_profile); - static void RecordMMap(const void* pointer, - AllocValue* alloc_value, - DeepHeapProfile* deep_profile); // All RegionStats members in this class contain the bytes of virtual // memory and committed memory. @@ -304,11 +290,6 @@ class DeepHeapProfile { int buffer_size, char raw_buffer[]); - // Counts mmap allocations in |deep_profile|->num_mmap_allocations_. - static void CountMMap(const void* pointer, - AllocValue* alloc_value, - DeepHeapProfile* deep_profile); - MemoryResidenceInfoGetterInterface* memory_residence_info_getter_; // Process ID of the last dump. This can change by fork. @@ -320,9 +301,6 @@ class DeepHeapProfile { char* profiler_buffer_; // Buffer we use many times. DeepBucketTable deep_table_; - MMapListEntry* mmap_list_; - int mmap_list_length_; - int num_mmap_allocations_; #endif // DEEP_HEAP_PROFILE HeapProfileTable* heap_profile_; diff --git a/third_party/tcmalloc/chromium/src/heap-checker.cc b/third_party/tcmalloc/chromium/src/heap-checker.cc index c53646b..ae69c7b 100644 --- a/third_party/tcmalloc/chromium/src/heap-checker.cc +++ b/third_party/tcmalloc/chromium/src/heap-checker.cc @@ -2203,7 +2203,7 @@ void HeapLeakChecker::BeforeConstructorsLocked() { RAW_CHECK(MallocHook::AddNewHook(&NewHook), ""); RAW_CHECK(MallocHook::AddDeleteHook(&DeleteHook), ""); constructor_heap_profiling = true; - MemoryRegionMap::Init(1); + MemoryRegionMap::Init(1, /* use_buckets */ false); // Set up MemoryRegionMap with (at least) one caller stack frame to record // (important that it's done before HeapProfileTable creation below). Allocator::Init(); diff --git a/third_party/tcmalloc/chromium/src/heap-profile-stats.h b/third_party/tcmalloc/chromium/src/heap-profile-stats.h new file mode 100644 index 0000000..e65cce2 --- /dev/null +++ b/third_party/tcmalloc/chromium/src/heap-profile-stats.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013 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. + +// This file defines structs to accumulate memory allocation and deallocation +// counts. These structs are commonly used for malloc (in HeapProfileTable) +// and mmap (in MemoryRegionMap). + +// A bucket is data structure for heap profiling to store a pair of a stack +// trace and counts of (de)allocation. Buckets are stored in a hash table +// which is declared as "HeapProfileBucket**". +// +// A hash value is computed from a stack trace. Collision in the hash table +// is resolved by separate chaining with linked lists. The links in the list +// are implemented with the member "HeapProfileBucket* next". +// +// A structure of a hash table HeapProfileBucket** bucket_table would be like: +// bucket_table[0] => NULL +// bucket_table[1] => HeapProfileBucket() => HeapProfileBucket() => NULL +// ... +// bucket_table[i] => HeapProfileBucket() => NULL +// ... +// bucket_table[n] => HeapProfileBucket() => NULL + +#ifndef HEAP_PROFILE_STATS_H_ +#define HEAP_PROFILE_STATS_H_ + +struct HeapProfileStats { + // Returns true if the two HeapProfileStats are semantically equal. + bool Equivalent(const HeapProfileStats& other) const { + return allocs - frees == other.allocs - other.frees && + alloc_size - free_size == other.alloc_size - other.free_size; + } + + int32 allocs; // Number of allocation calls. + int32 frees; // Number of free calls. + int64 alloc_size; // Total size of all allocated objects so far. + int64 free_size; // Total size of all freed objects so far. +}; + +// Allocation and deallocation statistics per each stack trace. +struct HeapProfileBucket : public HeapProfileStats { + // Longest stack trace we record. + static const int kMaxStackDepth = 32; + + uintptr_t hash; // Hash value of the stack trace. + int depth; // Depth of stack trace. + const void** stack; // Stack trace. + HeapProfileBucket* next; // Next entry in hash-table. +}; + +#endif // HEAP_PROFILE_STATS_H_ diff --git a/third_party/tcmalloc/chromium/src/heap-profile-table.cc b/third_party/tcmalloc/chromium/src/heap-profile-table.cc index 0b02d4c..68ec573 100644 --- a/third_party/tcmalloc/chromium/src/heap-profile-table.cc +++ b/third_party/tcmalloc/chromium/src/heap-profile-table.cc @@ -102,8 +102,7 @@ const char HeapProfileTable::kFileExt[] = ".heap"; //---------------------------------------------------------------------- -// Size for alloc_table_ and mmap_table_. -static const int kHashTableSize = 179999; +static const int kHashTableSize = 179999; // Size for bucket_table_. /*static*/ const int HeapProfileTable::kMaxStackDepth; //---------------------------------------------------------------------- @@ -125,61 +124,50 @@ static bool ByAllocatedSpace(HeapProfileTable::Stats* a, //---------------------------------------------------------------------- -HeapProfileTable::HeapProfileTable(Allocator alloc, DeAllocator dealloc) +HeapProfileTable::HeapProfileTable(Allocator alloc, + DeAllocator dealloc, + bool profile_mmap) : alloc_(alloc), dealloc_(dealloc), - num_alloc_buckets_(0), - mmap_table_(NULL), - num_available_mmap_buckets_(0), - mmap_address_map_(NULL) { - // Initialize the overall profile stats. - memset(&total_, 0, sizeof(total_)); - - // Make the malloc table. - const int alloc_table_bytes = kHashTableSize * sizeof(*alloc_table_); - alloc_table_ = reinterpret_cast<Bucket**>(alloc_(alloc_table_bytes)); - memset(alloc_table_, 0, alloc_table_bytes); - - // Make malloc and mmap allocation maps. - alloc_address_map_ = + bucket_table_(NULL), + profile_mmap_(profile_mmap), + num_buckets_(0), + address_map_(NULL) { + // Make a hash table for buckets. + const int table_bytes = kHashTableSize * sizeof(*bucket_table_); + bucket_table_ = static_cast<Bucket**>(alloc_(table_bytes)); + memset(bucket_table_, 0, table_bytes); + + // Make an allocation map. + address_map_ = new(alloc_(sizeof(AllocationMap))) AllocationMap(alloc_, dealloc_); -} -HeapProfileTable::~HeapProfileTable() { - DeallocateBucketTable(alloc_table_); - alloc_table_ = NULL; - DeallocateBucketTable(mmap_table_); - mmap_table_ = NULL; - DeallocateAllocationMap(alloc_address_map_); - alloc_address_map_ = NULL; - DeallocateAllocationMap(mmap_address_map_); - mmap_address_map_ = NULL; -} - -void HeapProfileTable::DeallocateAllocationMap(AllocationMap* allocation) { - if (allocation != NULL) { - alloc_address_map_->~AllocationMap(); - dealloc_(allocation); - } + // Initialize. + memset(&total_, 0, sizeof(total_)); + num_buckets_ = 0; } -void HeapProfileTable::DeallocateBucketTable(Bucket** table) { - if (table != NULL) { - for (int b = 0; b < kHashTableSize; b++) { - for (Bucket* x = table[b]; x != 0; /**/) { - Bucket* b = x; - x = x->next; - dealloc_(b->stack); - dealloc_(b); - } +HeapProfileTable::~HeapProfileTable() { + // Free the allocation map. + address_map_->~AllocationMap(); + dealloc_(address_map_); + address_map_ = NULL; + + // Free the hash table. + for (int i = 0; i < kHashTableSize; i++) { + for (Bucket* curr = bucket_table_[i]; curr != 0; /**/) { + Bucket* bucket = curr; + curr = curr->next; + dealloc_(bucket->stack); + dealloc_(bucket); } - dealloc_(table); } + dealloc_(bucket_table_); + bucket_table_ = NULL; } -HeapProfileTable::Bucket* HeapProfileTable::GetBucket( - int depth, const void* const key[], Bucket** table, - int* bucket_count) { +HeapProfileTable::Bucket* HeapProfileTable::GetBucket(int depth, + const void* const key[]) { // Make hash-value uintptr_t h = 0; for (int i = 0; i < depth; i++) { @@ -192,7 +180,7 @@ HeapProfileTable::Bucket* HeapProfileTable::GetBucket( // Lookup stack trace in table unsigned int buck = ((unsigned int) h) % kHashTableSize; - for (Bucket* b = table[buck]; b != 0; b = b->next) { + for (Bucket* b = bucket_table_[buck]; b != 0; b = b->next) { if ((b->hash == h) && (b->depth == depth) && equal(key, key + depth, b->stack)) { @@ -209,11 +197,9 @@ HeapProfileTable::Bucket* HeapProfileTable::GetBucket( b->hash = h; b->depth = depth; b->stack = kcopy; - b->next = table[buck]; - table[buck] = b; - if (bucket_count != NULL) { - ++(*bucket_count); - } + b->next = bucket_table_[buck]; + bucket_table_[buck] = b; + num_buckets_++; return b; } @@ -226,8 +212,7 @@ int HeapProfileTable::GetCallerStackTrace( void HeapProfileTable::RecordAlloc( const void* ptr, size_t bytes, int stack_depth, const void* const call_stack[]) { - Bucket* b = GetBucket(stack_depth, call_stack, alloc_table_, - &num_alloc_buckets_); + Bucket* b = GetBucket(stack_depth, call_stack); b->allocs++; b->alloc_size += bytes; total_.allocs++; @@ -236,12 +221,12 @@ void HeapProfileTable::RecordAlloc( AllocValue v; v.set_bucket(b); // also did set_live(false); set_ignore(false) v.bytes = bytes; - alloc_address_map_->Insert(ptr, v); + address_map_->Insert(ptr, v); } void HeapProfileTable::RecordFree(const void* ptr) { AllocValue v; - if (alloc_address_map_->FindAndRemove(ptr, &v)) { + if (address_map_->FindAndRemove(ptr, &v)) { Bucket* b = v.bucket(); b->frees++; b->free_size += v.bytes; @@ -251,14 +236,14 @@ void HeapProfileTable::RecordFree(const void* ptr) { } bool HeapProfileTable::FindAlloc(const void* ptr, size_t* object_size) const { - const AllocValue* alloc_value = alloc_address_map_->Find(ptr); + const AllocValue* alloc_value = address_map_->Find(ptr); if (alloc_value != NULL) *object_size = alloc_value->bytes; return alloc_value != NULL; } bool HeapProfileTable::FindAllocDetails(const void* ptr, AllocInfo* info) const { - const AllocValue* alloc_value = alloc_address_map_->Find(ptr); + const AllocValue* alloc_value = address_map_->Find(ptr); if (alloc_value != NULL) { info->object_size = alloc_value->bytes; info->call_stack = alloc_value->bucket()->stack; @@ -272,13 +257,13 @@ bool HeapProfileTable::FindInsideAlloc(const void* ptr, const void** object_ptr, size_t* object_size) const { const AllocValue* alloc_value = - alloc_address_map_->FindInside(&AllocValueSize, max_size, ptr, object_ptr); + address_map_->FindInside(&AllocValueSize, max_size, ptr, object_ptr); if (alloc_value != NULL) *object_size = alloc_value->bytes; return alloc_value != NULL; } bool HeapProfileTable::MarkAsLive(const void* ptr) { - AllocValue* alloc = alloc_address_map_->FindMutable(ptr); + AllocValue* alloc = address_map_->FindMutable(ptr); if (alloc && !alloc->live()) { alloc->set_live(true); return true; @@ -287,7 +272,7 @@ bool HeapProfileTable::MarkAsLive(const void* ptr) { } void HeapProfileTable::MarkAsIgnored(const void* ptr) { - AllocValue* alloc = alloc_address_map_->FindMutable(ptr); + AllocValue* alloc = address_map_->FindMutable(ptr); if (alloc) { alloc->set_ignore(true); } @@ -296,18 +281,18 @@ void HeapProfileTable::MarkAsIgnored(const void* ptr) { void HeapProfileTable::IterateAllocationAddresses(AddressIterator f, void* data) { const AllocationAddressIteratorArgs args(f, data); - alloc_address_map_->Iterate<const AllocationAddressIteratorArgs&>( + address_map_->Iterate<const AllocationAddressIteratorArgs&>( AllocationAddressesIterator, args); } void HeapProfileTable::MarkCurrentAllocations(AllocationMark mark) { const MarkArgs args(mark, true); - alloc_address_map_->Iterate<const MarkArgs&>(MarkIterator, args); + address_map_->Iterate<const MarkArgs&>(MarkIterator, args); } void HeapProfileTable::MarkUnmarkedAllocations(AllocationMark mark) { const MarkArgs args(mark, true); - alloc_address_map_->Iterate<const MarkArgs&>(MarkIterator, args); + address_map_->Iterate<const MarkArgs&>(MarkIterator, args); } // We'd be happier using snprintfer, but we don't to reduce dependencies. @@ -345,77 +330,21 @@ int HeapProfileTable::UnparseBucket(const Bucket& b, HeapProfileTable::Bucket** HeapProfileTable::MakeSortedBucketList() const { - Bucket** list = reinterpret_cast<Bucket**>(alloc_(sizeof(Bucket) * - (num_alloc_buckets_ + num_available_mmap_buckets_))); - - RAW_DCHECK(mmap_table_ != NULL || num_available_mmap_buckets_ == 0, ""); - - int n = 0; + Bucket** list = static_cast<Bucket**>(alloc_(sizeof(Bucket) * num_buckets_)); - for (int b = 0; b < kHashTableSize; b++) { - for (Bucket* x = alloc_table_[b]; x != 0; x = x->next) { - list[n++] = x; + int bucket_count = 0; + for (int i = 0; i < kHashTableSize; i++) { + for (Bucket* curr = bucket_table_[i]; curr != 0; curr = curr->next) { + list[bucket_count++] = curr; } } - RAW_DCHECK(n == num_alloc_buckets_, ""); + RAW_DCHECK(bucket_count == num_buckets_, ""); - if (mmap_table_ != NULL) { - for (int b = 0; b < kHashTableSize; b++) { - for (Bucket* x = mmap_table_[b]; x != 0; x = x->next) { - list[n++] = x; - } - } - } - RAW_DCHECK(n == num_alloc_buckets_ + num_available_mmap_buckets_, ""); - - sort(list, list + num_alloc_buckets_ + num_available_mmap_buckets_, - ByAllocatedSpace); + sort(list, list + num_buckets_, ByAllocatedSpace); return list; } -void HeapProfileTable::RefreshMMapData(Allocator mmap_alloc, - DeAllocator mmap_dealloc) { - // Make the table - static const int mmap_table_bytes = kHashTableSize * sizeof(*mmap_table_); - if (mmap_table_ == NULL) { - mmap_table_ = reinterpret_cast<Bucket**>(alloc_(mmap_table_bytes)); - memset(mmap_table_, 0, mmap_table_bytes); - } - num_available_mmap_buckets_ = 0; - - ClearMMapData(); - mmap_address_map_ = new(alloc_(sizeof(AllocationMap))) - AllocationMap(mmap_alloc, mmap_dealloc); - - MemoryRegionMap::LockHolder l; - for (MemoryRegionMap::RegionIterator r = - MemoryRegionMap::BeginRegionLocked(); - r != MemoryRegionMap::EndRegionLocked(); ++r) { - Bucket* b = - GetBucket(r->call_stack_depth, r->call_stack, mmap_table_, NULL); - if (b->alloc_size == 0) { - num_available_mmap_buckets_ += 1; - } - b->allocs += 1; - b->alloc_size += r->end_addr - r->start_addr; - - AllocValue v; - v.set_bucket(b); - v.bytes = r->end_addr - r->start_addr; - mmap_address_map_->Insert(reinterpret_cast<const void*>(r->start_addr), v); - } -} - -void HeapProfileTable::ClearMMapData() { - if (mmap_address_map_ == NULL) return; - - mmap_address_map_->Iterate(ZeroBucketCountsIterator, this); - mmap_address_map_->~AllocationMap(); - dealloc_(mmap_address_map_); - mmap_address_map_ = NULL; -} - void HeapProfileTable::DumpMarkedObjects(AllocationMark mark, const char* file_name) { RawFD fd = RawOpenForWriting(file_name); @@ -424,7 +353,7 @@ void HeapProfileTable::DumpMarkedObjects(AllocationMark mark, return; } const DumpMarkedArgs args(fd, mark); - alloc_address_map_->Iterate<const DumpMarkedArgs&>(DumpMarkedIterator, args); + address_map_->Iterate<const DumpMarkedArgs&>(DumpMarkedIterator, args); RawClose(fd); } @@ -439,7 +368,7 @@ void HeapProfileTable::DumpTypeStatistics(const char* file_name) const { AddressMap<TypeCount>* type_size_map; type_size_map = new(alloc_(sizeof(AddressMap<TypeCount>))) AddressMap<TypeCount>(alloc_, dealloc_); - alloc_address_map_->Iterate(TallyTypesItererator, type_size_map); + address_map_->Iterate(TallyTypesItererator, type_size_map); RawWrite(fd, kTypeProfileStatsHeader, strlen(kTypeProfileStatsHeader)); const DumpArgs args(fd, NULL); @@ -455,7 +384,7 @@ void HeapProfileTable::IterateOrderedAllocContexts( AllocContextIterator callback) const { Bucket** list = MakeSortedBucketList(); AllocContextInfo info; - for (int i = 0; i < num_alloc_buckets_; ++i) { + for (int i = 0; i < num_buckets_; ++i) { *static_cast<Stats*>(&info) = *static_cast<Stats*>(list[i]); info.stack_depth = list[i]->depth; info.call_stack = list[i]->stack; @@ -487,14 +416,17 @@ int HeapProfileTable::FillOrderedProfile(char buf[], int size) const { memset(&stats, 0, sizeof(stats)); int bucket_length = snprintf(buf, size, "%s", kProfileHeader); if (bucket_length < 0 || bucket_length >= size) return 0; - Bucket total_with_mmap(total_); - if (mmap_table_ != NULL) { - total_with_mmap.alloc_size += MemoryRegionMap::MapSize(); - total_with_mmap.free_size += MemoryRegionMap::UnmapSize(); - } - bucket_length = UnparseBucket(total_with_mmap, buf, bucket_length, size, + bucket_length = UnparseBucket(total_, buf, bucket_length, size, " heapprofile", &stats); - for (int i = 0; i < num_alloc_buckets_; i++) { + + // Dump the mmap list first. + if (profile_mmap_) { + BufferArgs buffer(buf, bucket_length, size); + MemoryRegionMap::IterateBuckets<BufferArgs*>(DumpBucketIterator, &buffer); + bucket_length = buffer.buflen; + } + + for (int i = 0; i < num_buckets_; i++) { bucket_length = UnparseBucket(*list[i], buf, bucket_length, size, "", &stats); } @@ -508,6 +440,13 @@ int HeapProfileTable::FillOrderedProfile(char buf[], int size) const { return bucket_length + map_length; } +// static +void HeapProfileTable::DumpBucketIterator(const Bucket* bucket, + BufferArgs* args) { + args->buflen = UnparseBucket(*bucket, args->buf, args->buflen, args->bufsize, + "", NULL); +} + #if defined(TYPE_PROFILING) // static void HeapProfileTable::TallyTypesItererator( @@ -598,17 +537,6 @@ void HeapProfileTable::MarkIterator(const void* ptr, AllocValue* v, v->set_mark(args.mark); } -inline void HeapProfileTable::ZeroBucketCountsIterator( - const void* ptr, AllocValue* v, HeapProfileTable* heap_profile) { - Bucket* b = v->bucket(); - if (b != NULL) { - b->allocs = 0; - b->alloc_size = 0; - b->free_size = 0; - b->frees = 0; - } -} - // Callback from NonLiveSnapshot; adds entry to arg->dest // if not the entry is not live and is not present in arg->base. void HeapProfileTable::AddIfNonLive(const void* ptr, AllocValue* v, @@ -675,7 +603,7 @@ void HeapProfileTable::CleanupOldProfiles(const char* prefix) { HeapProfileTable::Snapshot* HeapProfileTable::TakeSnapshot() { Snapshot* s = new (alloc_(sizeof(Snapshot))) Snapshot(alloc_, dealloc_); - alloc_address_map_->Iterate(AddToSnapshot, s); + address_map_->Iterate(AddToSnapshot, s); return s; } @@ -700,7 +628,7 @@ HeapProfileTable::Snapshot* HeapProfileTable::NonLiveSnapshot( AddNonLiveArgs args; args.dest = s; args.base = base; - alloc_address_map_->Iterate<AddNonLiveArgs*>(AddIfNonLive, &args); + address_map_->Iterate<AddNonLiveArgs*>(AddIfNonLive, &args); RAW_VLOG(2, "NonLiveSnapshot output: %d %d\n", int(s->total_.allocs - s->total_.frees), int(s->total_.alloc_size - s->total_.free_size)); diff --git a/third_party/tcmalloc/chromium/src/heap-profile-table.h b/third_party/tcmalloc/chromium/src/heap-profile-table.h index 29f0d36..c2ad39f 100644 --- a/third_party/tcmalloc/chromium/src/heap-profile-table.h +++ b/third_party/tcmalloc/chromium/src/heap-profile-table.h @@ -38,6 +38,7 @@ #include "addressmap-inl.h" #include "base/basictypes.h" #include "base/logging.h" // for RawFD +#include "heap-profile-stats.h" #if defined(TYPE_PROFILING) #include <gperftools/type_profiler_map.h> @@ -62,18 +63,7 @@ class HeapProfileTable { // data types ---------------------------- // Profile stats. - struct Stats { - int32 allocs; // Number of allocation calls - int32 frees; // Number of free calls - int64 alloc_size; // Total size of all allocated objects so far - int64 free_size; // Total size of all freed objects so far - - // semantic equality - bool Equivalent(const Stats& x) const { - return allocs - frees == x.allocs - x.frees && - alloc_size - free_size == x.alloc_size - x.free_size; - } - }; + typedef HeapProfileStats Stats; // Possible marks for MarkCurrentAllocations and MarkUnmarkedAllocations. New // allocations are marked with UNMARKED by default. @@ -107,7 +97,7 @@ class HeapProfileTable { // interface --------------------------- - HeapProfileTable(Allocator alloc, DeAllocator dealloc); + HeapProfileTable(Allocator alloc, DeAllocator dealloc, bool profile_mmap); ~HeapProfileTable(); // Collect the stack trace for the function that asked to do the @@ -169,7 +159,7 @@ class HeapProfileTable { // Iterate over the allocation profile data calling "callback" // for every allocation. void IterateAllocs(AllocIterator callback) const { - alloc_address_map_->Iterate(MapArgsAllocIterator, callback); + address_map_->Iterate(MapArgsAllocIterator, callback); } // Callback for iterating through addresses of all allocated objects. Accepts @@ -214,22 +204,6 @@ class HeapProfileTable { // Caller must call ReleaseSnapshot() on result when no longer needed. Snapshot* NonLiveSnapshot(Snapshot* base); - // Refresh the internal mmap information from MemoryRegionMap. Results of - // FillOrderedProfile and IterateOrderedAllocContexts will contain mmap'ed - // memory regions as at calling RefreshMMapData. - // 'mmap_alloc' is an allocator for an address map. A function which calls - // LowLevelAlloc::AllocWithArena is expected like the constractor. - // 'mmap_dealloc' is a corresponding deallocator to 'mmap_alloc'. - // They are introduced to avoid expected memory fragmentation and bloat in - // an arena. A dedicated arena for this function allows disposing whole the - // arena after ClearMMapData. - void RefreshMMapData(Allocator mmap_alloc, DeAllocator mmap_dealloc); - - // Clear the internal mmap information. Results of FillOrderedProfile and - // IterateOrderedAllocContexts won't contain mmap'ed memory regions after - // calling ClearMMapData. - void ClearMMapData(); - // Dump a list of allocations marked as "live" along with their creation // stack traces and sizes to a file named |file_name|. Together with // MarkCurrentAllocatiosn and MarkUnmarkedAllocations this can be used @@ -260,12 +234,7 @@ class HeapProfileTable { // Hash table bucket to hold (de)allocation stats // for a given allocation call stack trace. - struct Bucket : public Stats { - uintptr_t hash; // Hash value of the stack trace - int depth; // Depth of stack trace - const void** stack; // Stack trace - Bucket* next; // Next entry in hash-table - }; + typedef HeapProfileBucket Bucket; // Info stored in the address map struct AllocValue { @@ -310,52 +279,75 @@ class HeapProfileTable { typedef AddressMap<AllocValue> AllocationMap; + // Arguments that need to be passed DumpBucketIterator callback below. + struct BufferArgs { + BufferArgs(char* buf_arg, int buflen_arg, int bufsize_arg) + : buf(buf_arg), + buflen(buflen_arg), + bufsize(bufsize_arg) { + } + + char* buf; + int buflen; + int bufsize; + + DISALLOW_COPY_AND_ASSIGN(BufferArgs); + }; + // Arguments that need to be passed DumpNonLiveIterator callback below. struct DumpArgs { + DumpArgs(RawFD fd_arg, Stats* profile_stats_arg) + : fd(fd_arg), + profile_stats(profile_stats_arg) { + } + RawFD fd; // file to write to Stats* profile_stats; // stats to update (may be NULL) - - DumpArgs(RawFD a, Stats* d) - : fd(a), profile_stats(d) { } }; // Arguments that need to be passed DumpMarkedIterator callback below. struct DumpMarkedArgs { + DumpMarkedArgs(RawFD fd_arg, AllocationMark mark_arg) + : fd(fd_arg), + mark(mark_arg) { + } + RawFD fd; // file to write to. AllocationMark mark; // The mark of the allocations to process. - - DumpMarkedArgs(RawFD a, AllocationMark m) : fd(a), mark(m) { } }; // Arguments that need to be passed MarkIterator callback below. struct MarkArgs { + MarkArgs(AllocationMark mark_arg, bool mark_all_arg) + : mark(mark_arg), + mark_all(mark_all_arg) { + } + AllocationMark mark; // The mark to put on allocations. bool mark_all; // True if all allocations should be marked. Otherwise just // mark unmarked allocations. - - MarkArgs(AllocationMark m, bool a) : mark(m), mark_all(a) { } }; #if defined(TYPE_PROFILING) struct TypeCount { - size_t bytes; - unsigned int objects; - TypeCount(size_t bytes_arg, unsigned int objects_arg) : bytes(bytes_arg), objects(objects_arg) { } + + size_t bytes; + unsigned int objects; }; #endif // defined(TYPE_PROFILING) struct AllocationAddressIteratorArgs { + AllocationAddressIteratorArgs(AddressIterator callback_arg, void* data_arg) + : callback(callback_arg), + data(data_arg) { + } + AddressIterator callback; void* data; - - AllocationAddressIteratorArgs(AddressIterator iterator, void* d) - : callback(iterator), - data(d) { - } }; // helpers ---------------------------- @@ -376,18 +368,9 @@ class HeapProfileTable { const char* extra, Stats* profile_stats); - // Deallocate a given allocation map. - void DeallocateAllocationMap(AllocationMap* allocation); - - // Deallocate a given bucket table. - void DeallocateBucketTable(Bucket** table); - - // Get the bucket for the caller stack trace 'key' of depth 'depth' from a - // bucket hash map 'table' creating the bucket if needed. '*bucket_count' - // is incremented both when 'bucket_count' is not NULL and when a new - // bucket object is created. - Bucket* GetBucket(int depth, const void* const key[], Bucket** table, - int* bucket_count); + // Get the bucket for the caller stack trace 'key' of depth 'depth' + // creating the bucket if needed. + Bucket* GetBucket(int depth, const void* const key[]); // Helper for IterateAllocs to do callback signature conversion // from AllocationMap::Iterate to AllocIterator. @@ -402,6 +385,10 @@ class HeapProfileTable { callback(ptr, info); } + // Helper to dump a bucket. + inline static void DumpBucketIterator(const Bucket* bucket, + BufferArgs* args); + // Helper for IterateAllocationAddresses. inline static void AllocationAddressesIterator( const void* ptr, @@ -422,10 +409,6 @@ class HeapProfileTable { inline static void DumpMarkedIterator(const void* ptr, AllocValue* v, const DumpMarkedArgs& args); - // Helper for filling size variables in buckets by zero. - inline static void ZeroBucketCountsIterator( - const void* ptr, AllocValue* v, HeapProfileTable* heap_profile); - #if defined(TYPE_PROFILING) inline static void TallyTypesItererator(const void* ptr, AllocValue* value, @@ -437,8 +420,7 @@ class HeapProfileTable { #endif // defined(TYPE_PROFILING) // Helper for IterateOrderedAllocContexts and FillOrderedProfile. - // Creates a sorted list of Buckets whose length is num_alloc_buckets_ + - // num_avaliable_mmap_buckets_. + // Creates a sorted list of Buckets whose length is num_buckets_. // The caller is responsible for deallocating the returned list. Bucket** MakeSortedBucketList() const; @@ -471,25 +453,19 @@ class HeapProfileTable { // Overall profile stats; we use only the Stats part, // but make it a Bucket to pass to UnparseBucket. - // It doesn't contain mmap'ed regions. Bucket total_; + bool profile_mmap_; + // Bucket hash table for malloc. // We hand-craft one instead of using one of the pre-written // ones because we do not want to use malloc when operating on the table. // It is only few lines of code, so no big deal. - Bucket** alloc_table_; - int num_alloc_buckets_; - - // Bucket hash table for mmap. - // This table is filled with the information from MemoryRegionMap by calling - // RefreshMMapData. - Bucket** mmap_table_; - int num_available_mmap_buckets_; + Bucket** bucket_table_; + int num_buckets_; // Map of all currently allocated objects and mapped regions we know about. - AllocationMap* alloc_address_map_; - AllocationMap* mmap_address_map_; + AllocationMap* address_map_; DISALLOW_COPY_AND_ASSIGN(HeapProfileTable); }; diff --git a/third_party/tcmalloc/chromium/src/heap-profiler.cc b/third_party/tcmalloc/chromium/src/heap-profiler.cc index 4aee0cf..512dc7c 100644 --- a/third_party/tcmalloc/chromium/src/heap-profiler.cc +++ b/third_party/tcmalloc/chromium/src/heap-profiler.cc @@ -157,36 +157,6 @@ static void ProfilerFree(void* p) { LowLevelAlloc::Free(p); } -//---------------------------------------------------------------------- -// Another allocator for heap profiler's internal mmap address map -// -// Large amount of memory is consumed if we use an arena 'heap_profiler_memory' -// for the internal mmap address map. It looks like memory fragmentation -// because of repeated allocation/deallocation in the arena. -// -// 'mmap_heap_profiler_memory' is a dedicated arena for the mmap address map. -// This arena is reserved for every construction of the mmap address map, and -// disposed after every use. -//---------------------------------------------------------------------- - -static LowLevelAlloc::Arena* mmap_heap_profiler_memory = NULL; - -static void* MMapProfilerMalloc(size_t bytes) { - return LowLevelAlloc::AllocWithArena(bytes, mmap_heap_profiler_memory); -} -static void MMapProfilerFree(void* p) { - LowLevelAlloc::Free(p); -} - -// This function should be called from a locked scope. -// It returns false if failed in deleting the arena. -static bool DeleteMMapProfilerArenaIfExistsLocked() { - if (mmap_heap_profiler_memory == NULL) return true; - if (!LowLevelAlloc::DeleteArena(mmap_heap_profiler_memory)) return false; - mmap_heap_profiler_memory = NULL; - return true; -} - // We use buffers of this size in DoGetHeapProfile. // The size is 1 << 20 in the original google-perftools. Changed it to // 5 << 20 since a larger buffer is requried for deeper profiling in Chromium. @@ -234,25 +204,18 @@ static char* DoGetHeapProfileLocked(char* buf, int buflen) { RAW_DCHECK(heap_lock.IsHeld(), ""); int bytes_written = 0; if (is_on) { - if (FLAGS_mmap_profile) { - if (!DeleteMMapProfilerArenaIfExistsLocked()) { - RAW_LOG(FATAL, "Memory leak in HeapProfiler:"); - } - mmap_heap_profiler_memory = - LowLevelAlloc::NewArena(0, LowLevelAlloc::DefaultArena()); - heap_profile->RefreshMMapData(MMapProfilerMalloc, MMapProfilerFree); - } + HeapProfileTable::Stats const stats = heap_profile->total(); + (void)stats; // avoid an unused-variable warning in non-debug mode. if (deep_profile) { bytes_written = deep_profile->FillOrderedProfile(buf, buflen - 1); } else { bytes_written = heap_profile->FillOrderedProfile(buf, buflen - 1); } - if (FLAGS_mmap_profile) { - heap_profile->ClearMMapData(); - if (!DeleteMMapProfilerArenaIfExistsLocked()) { - RAW_LOG(FATAL, "Memory leak in HeapProfiler:"); - } - } + // FillOrderedProfile should not reduce the set of active mmap-ed regions, + // hence MemoryRegionMap will let us remove everything we've added above: + RAW_DCHECK(stats.Equivalent(heap_profile->total()), ""); + // if this fails, we somehow removed by FillOrderedProfile + // more than we have added. } buf[bytes_written] = '\0'; RAW_DCHECK(bytes_written == strlen(buf), ""); @@ -502,7 +465,8 @@ extern "C" void HeapProfilerStart(const char* prefix) { if (FLAGS_mmap_profile) { // Ask MemoryRegionMap to record all mmap, mremap, and sbrk // call stack traces of at least size kMaxStackDepth: - MemoryRegionMap::Init(HeapProfileTable::kMaxStackDepth); + MemoryRegionMap::Init(HeapProfileTable::kMaxStackDepth, + /* use_buckets */ true); } if (FLAGS_mmap_log) { @@ -522,7 +486,7 @@ extern "C" void HeapProfilerStart(const char* prefix) { reinterpret_cast<char*>(ProfilerMalloc(kProfileBufferSize)); heap_profile = new(ProfilerMalloc(sizeof(HeapProfileTable))) - HeapProfileTable(ProfilerMalloc, ProfilerFree); + HeapProfileTable(ProfilerMalloc, ProfilerFree, FLAGS_mmap_profile); last_dump_alloc = 0; last_dump_free = 0; @@ -594,9 +558,6 @@ extern "C" void HeapProfilerStop() { // free profile heap_profile->~HeapProfileTable(); - if (!DeleteMMapProfilerArenaIfExistsLocked()) { - RAW_LOG(FATAL, "Memory leak in HeapProfiler:"); - } ProfilerFree(heap_profile); heap_profile = NULL; diff --git a/third_party/tcmalloc/chromium/src/memory_region_map.cc b/third_party/tcmalloc/chromium/src/memory_region_map.cc index 1a81172..dcc73c4 100644 --- a/third_party/tcmalloc/chromium/src/memory_region_map.cc +++ b/third_party/tcmalloc/chromium/src/memory_region_map.cc @@ -84,9 +84,10 @@ // which (sometimes) causes mmap, which calls our hook, and so on. // We do this as follows: on a recursive call of MemoryRegionMap's // mmap/sbrk/mremap hook we record the data about the allocation in a -// static fixed-sized stack (saved_regions), when the recursion unwinds -// but before returning from the outer hook call we unwind this stack and -// move the data from saved_regions to its permanent place in the RegionSet, +// static fixed-sized stack (saved_regions and saved_buckets), when the +// recursion unwinds but before returning from the outer hook call we unwind +// this stack and move the data from saved_regions and saved_buckets to its +// permanent place in the RegionSet and "bucket_table" respectively, // which can cause more allocations and mmap-s and recursion and unwinding, // but the whole process ends eventually due to the fact that for the small // allocations we are doing LowLevelAlloc reuses one mmap call and parcels out @@ -147,6 +148,13 @@ int MemoryRegionMap::recursion_count_ = 0; // GUARDED_BY(owner_lock_) pthread_t MemoryRegionMap::lock_owner_tid_; // GUARDED_BY(owner_lock_) int64 MemoryRegionMap::map_size_ = 0; int64 MemoryRegionMap::unmap_size_ = 0; +HeapProfileBucket** MemoryRegionMap::bucket_table_ = NULL; // GUARDED_BY(lock_) +int MemoryRegionMap::num_buckets_ = 0; // GUARDED_BY(lock_) +int MemoryRegionMap::saved_buckets_count_ = 0; // GUARDED_BY(lock_) +HeapProfileBucket MemoryRegionMap::saved_buckets_[20]; // GUARDED_BY(lock_) + +// GUARDED_BY(lock_) +const void* MemoryRegionMap::saved_buckets_keys_[20][kMaxStackDepth]; // ========================================================================= // @@ -182,7 +190,7 @@ static MemoryRegionMap::RegionSetRep regions_rep; // (or rather should we *not* use regions_ to record a hooked mmap). static bool recursive_insert = false; -void MemoryRegionMap::Init(int max_stack_depth) { +void MemoryRegionMap::Init(int max_stack_depth, bool use_buckets) { RAW_VLOG(10, "MemoryRegionMap Init"); RAW_CHECK(max_stack_depth >= 0, ""); // Make sure we don't overflow the memory in region stacks: @@ -214,6 +222,15 @@ void MemoryRegionMap::Init(int max_stack_depth) { // Can't instead use HandleSavedRegionsLocked(&DoInsertRegionLocked) before // recursive_insert = false; as InsertRegionLocked will also construct // regions_ on demand for us. + if (use_buckets) { + const int table_bytes = kHashTableSize * sizeof(*bucket_table_); + recursive_insert = true; + bucket_table_ = static_cast<HeapProfileBucket**>( + MyAllocator::Allocate(table_bytes)); + recursive_insert = false; + memset(bucket_table_, 0, table_bytes); + num_buckets_ = 0; + } Unlock(); RAW_VLOG(10, "MemoryRegionMap Init done"); } @@ -228,6 +245,19 @@ bool MemoryRegionMap::Shutdown() { RAW_VLOG(10, "MemoryRegionMap Shutdown decrement done"); return true; } + if (bucket_table_ != NULL) { + for (int i = 0; i < kHashTableSize; i++) { + for (HeapProfileBucket* curr = bucket_table_[i]; curr != 0; /**/) { + HeapProfileBucket* bucket = curr; + curr = curr->next; + MyAllocator::Free(bucket->stack, 0); + MyAllocator::Free(bucket, 0); + } + } + MyAllocator::Free(bucket_table_, 0); + num_buckets_ = 0; + bucket_table_ = NULL; + } RAW_CHECK(MallocHook::RemoveMmapHook(&MmapHook), ""); RAW_CHECK(MallocHook::RemoveMremapHook(&MremapHook), ""); RAW_CHECK(MallocHook::RemoveSbrkHook(&SbrkHook), ""); @@ -245,6 +275,11 @@ bool MemoryRegionMap::Shutdown() { return deleted_arena; } +bool MemoryRegionMap::IsRecordingLocked() { + RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); + return client_count_ > 0; +} + // Invariants (once libpthread_initialized is true): // * While lock_ is not held, recursion_count_ is 0 (and // lock_owner_tid_ is the previous owner, but we don't rely on @@ -336,6 +371,62 @@ bool MemoryRegionMap::FindAndMarkStackRegion(uintptr_t stack_top, return region != NULL; } +HeapProfileBucket* MemoryRegionMap::GetBucket(int depth, + const void* const key[]) { + RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); + // Make hash-value + uintptr_t hash = 0; + for (int i = 0; i < depth; i++) { + hash += reinterpret_cast<uintptr_t>(key[i]); + hash += hash << 10; + hash ^= hash >> 6; + } + hash += hash << 3; + hash ^= hash >> 11; + + // Lookup stack trace in table + unsigned int hash_index = (static_cast<unsigned int>(hash)) % kHashTableSize; + for (HeapProfileBucket* bucket = bucket_table_[hash_index]; + bucket != 0; + bucket = bucket->next) { + if ((bucket->hash == hash) && (bucket->depth == depth) && + std::equal(key, key + depth, bucket->stack)) { + return bucket; + } + } + + // Create new bucket + const size_t key_size = sizeof(key[0]) * depth; + HeapProfileBucket* bucket; + if (recursive_insert) { // recursion: save in saved_buckets_ + const void** key_copy = saved_buckets_keys_[saved_buckets_count_]; + std::copy(key, key + depth, key_copy); + bucket = &saved_buckets_[saved_buckets_count_]; + memset(bucket, 0, sizeof(*bucket)); + ++saved_buckets_count_; + bucket->stack = key_copy; + bucket->next = NULL; + } else { + recursive_insert = true; + const void** key_copy = static_cast<const void**>( + MyAllocator::Allocate(key_size)); + recursive_insert = false; + std::copy(key, key + depth, key_copy); + recursive_insert = true; + bucket = static_cast<HeapProfileBucket*>( + MyAllocator::Allocate(sizeof(HeapProfileBucket))); + recursive_insert = false; + memset(bucket, 0, sizeof(*bucket)); + bucket->stack = key_copy; + bucket->next = bucket_table_[hash_index]; + } + bucket->hash = hash; + bucket->depth = depth; + bucket_table_[hash_index] = bucket; + ++num_buckets_; + return bucket; +} + MemoryRegionMap::RegionIterator MemoryRegionMap::BeginRegionLocked() { RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); RAW_CHECK(regions_ != NULL, ""); @@ -404,6 +495,44 @@ inline void MemoryRegionMap::HandleSavedRegionsLocked( } } +void MemoryRegionMap::RestoreSavedBucketsLocked() { + RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); + while (saved_buckets_count_ > 0) { + HeapProfileBucket bucket = saved_buckets_[--saved_buckets_count_]; + unsigned int hash_index = + static_cast<unsigned int>(bucket.hash) % kHashTableSize; + bool is_found = false; + for (HeapProfileBucket* curr = bucket_table_[hash_index]; + curr != 0; + curr = curr->next) { + if ((curr->hash == bucket.hash) && (curr->depth == bucket.depth) && + std::equal(bucket.stack, bucket.stack + bucket.depth, curr->stack)) { + curr->allocs += bucket.allocs; + curr->alloc_size += bucket.alloc_size; + curr->frees += bucket.frees; + curr->free_size += bucket.free_size; + is_found = true; + break; + } + } + if (is_found) continue; + + const size_t key_size = sizeof(bucket.stack[0]) * bucket.depth; + const void** key_copy = static_cast<const void**>( + MyAllocator::Allocate(key_size)); + std::copy(bucket.stack, bucket.stack + bucket.depth, key_copy); + HeapProfileBucket* new_bucket = static_cast<HeapProfileBucket*>( + MyAllocator::Allocate(sizeof(HeapProfileBucket))); + memset(new_bucket, 0, sizeof(*new_bucket)); + new_bucket->hash = bucket.hash; + new_bucket->depth = bucket.depth; + new_bucket->stack = key_copy; + new_bucket->next = bucket_table_[hash_index]; + bucket_table_[hash_index] = new_bucket; + ++num_buckets_; + } +} + inline void MemoryRegionMap::InsertRegionLocked(const Region& region) { RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); // We can be called recursively, because RegionSet constructor @@ -468,6 +597,16 @@ void MemoryRegionMap::RecordRegionAddition(const void* start, size_t size) { InsertRegionLocked(region); // This will (eventually) allocate storage for and copy over the stack data // from region.call_stack_data_ that is pointed by region.call_stack(). + if (bucket_table_ != NULL) { + HeapProfileBucket* b = GetBucket(depth, region.call_stack); + ++b->allocs; + b->alloc_size += size; + if (!recursive_insert) { + recursive_insert = true; + RestoreSavedBucketsLocked(); + recursive_insert = false; + } + } Unlock(); } @@ -486,6 +625,7 @@ void MemoryRegionMap::RecordRegionRemoval(const void* start, size_t size) { Region& r = saved_regions[i]; if (r.start_addr == start_addr && r.end_addr == end_addr) { // An exact match, so it's safe to remove. + RecordRegionRemovalInBucket(r.call_stack_depth, r.call_stack, size); --saved_regions_count; --put_pos; RAW_VLOG(10, ("Insta-Removing saved region %p..%p; " @@ -530,6 +670,8 @@ void MemoryRegionMap::RecordRegionRemoval(const void* start, size_t size) { RAW_VLOG(12, "Deleting region %p..%p", reinterpret_cast<void*>(region->start_addr), reinterpret_cast<void*>(region->end_addr)); + RecordRegionRemovalInBucket(region->call_stack_depth, region->call_stack, + region->end_addr - region->start_addr); RegionSet::iterator d = region; ++region; regions_->erase(d); @@ -539,6 +681,8 @@ void MemoryRegionMap::RecordRegionRemoval(const void* start, size_t size) { RAW_VLOG(12, "Splitting region %p..%p in two", reinterpret_cast<void*>(region->start_addr), reinterpret_cast<void*>(region->end_addr)); + RecordRegionRemovalInBucket(region->call_stack_depth, region->call_stack, + end_addr - start_addr); // Make another region for the start portion: // The new region has to be the start portion because we can't // just modify region->end_addr as it's the sorting key. @@ -552,12 +696,16 @@ void MemoryRegionMap::RecordRegionRemoval(const void* start, size_t size) { RAW_VLOG(12, "Start-chopping region %p..%p", reinterpret_cast<void*>(region->start_addr), reinterpret_cast<void*>(region->end_addr)); + RecordRegionRemovalInBucket(region->call_stack_depth, region->call_stack, + end_addr - region->start_addr); const_cast<Region&>(*region).set_start_addr(end_addr); } else if (start_addr > region->start_addr && start_addr < region->end_addr) { // cut from end RAW_VLOG(12, "End-chopping region %p..%p", reinterpret_cast<void*>(region->start_addr), reinterpret_cast<void*>(region->end_addr)); + RecordRegionRemovalInBucket(region->call_stack_depth, region->call_stack, + region->end_addr - start_addr); // Can't just modify region->end_addr (it's the sorting key): Region r = *region; r.set_end_addr(start_addr); @@ -580,6 +728,16 @@ void MemoryRegionMap::RecordRegionRemoval(const void* start, size_t size) { Unlock(); } +void MemoryRegionMap::RecordRegionRemovalInBucket(int depth, + const void* const stack[], + size_t size) { + RAW_CHECK(LockIsHeld(), "should be held (by this thread)"); + if (bucket_table_ == NULL) return; + HeapProfileBucket* b = GetBucket(depth, stack); + ++b->frees; + b->free_size += size; +} + void MemoryRegionMap::MmapHook(const void* result, const void* start, size_t size, int prot, int flags, diff --git a/third_party/tcmalloc/chromium/src/memory_region_map.h b/third_party/tcmalloc/chromium/src/memory_region_map.h index 988ea70..be191a9 100644 --- a/third_party/tcmalloc/chromium/src/memory_region_map.h +++ b/third_party/tcmalloc/chromium/src/memory_region_map.h @@ -45,6 +45,7 @@ #include "base/spinlock.h" #include "base/thread_annotations.h" #include "base/low_level_alloc.h" +#include "heap-profile-stats.h" // TODO(maxim): add a unittest: // execute a bunch of mmaps and compare memory map what strace logs @@ -72,6 +73,10 @@ class MemoryRegionMap { // don't take the address of it! static const int kMaxStackDepth = 32; + // Size of the hash table of buckets. A structure of the bucket table is + // described in heap-profile-stats.h. + static const int kHashTableSize = 179999; + public: // interface ================================================================ @@ -87,11 +92,14 @@ class MemoryRegionMap { // are automatically shrunk to "max_stack_depth" when they are recorded. // Init() can be called more than once w/o harm, largest max_stack_depth // will be the effective one. + // When "use_buckets" is true, then counts of mmap and munmap sizes will be + // recorded with each stack trace. If Init() is called more than once, then + // counting will be effective after any call contained "use_buckets" of true. // It will install mmap, munmap, mremap, sbrk hooks // and initialize arena_ and our hook and locks, hence one can use // MemoryRegionMap::Lock()/Unlock() to manage the locks. // Uses Lock/Unlock inside. - static void Init(int max_stack_depth); + static void Init(int max_stack_depth, bool use_buckets); // Try to shutdown this module undoing what Init() did. // Returns true iff could do full shutdown (or it was not attempted). @@ -99,6 +107,10 @@ class MemoryRegionMap { // the number of Init() calls. static bool Shutdown(); + // Return true if MemoryRegionMap is initialized and recording, i.e. when + // then number of Init() calls are more than the number of Shutdown() calls. + static bool IsRecordingLocked(); + // Locks to protect our internal data structures. // These also protect use of arena_ if our Init() has been done. // The lock is recursive. @@ -214,6 +226,18 @@ class MemoryRegionMap { // Returns success. Uses Lock/Unlock inside. static bool FindAndMarkStackRegion(uintptr_t stack_top, Region* result); + // Iterate over the buckets which store mmap and munmap counts per stack + // trace. It calls "callback" for each bucket, and passes "arg" to it. + template<class Type> + static void IterateBuckets(void (*callback)(const HeapProfileBucket*, Type), + Type arg); + + // Get the bucket whose caller stack trace is "key". The stack trace is + // used to a depth of "depth" at most. The requested bucket is created if + // needed. + // The bucket table is described in heap-profile-stats.h. + static HeapProfileBucket* GetBucket(int depth, const void* const key[]); + private: // our internal types ============================================== // Region comparator for sorting with STL @@ -280,7 +304,7 @@ class MemoryRegionMap { // simply by acquiring our recursive Lock() before that. static RegionSet* regions_; - // Lock to protect regions_ variable and the data behind. + // Lock to protect regions_ and buckets_ variables and the data behind. static SpinLock lock_; // Lock to protect the recursive lock itself. static SpinLock owner_lock_; @@ -295,6 +319,30 @@ class MemoryRegionMap { // Total size of all unmapped pages so far static int64 unmap_size_; + // Bucket hash table which is described in heap-profile-stats.h. + static HeapProfileBucket** bucket_table_ GUARDED_BY(lock_); + static int num_buckets_ GUARDED_BY(lock_); + + // The following members are local to MemoryRegionMap::GetBucket() + // and MemoryRegionMap::HandleSavedBucketsLocked() + // and are file-level to ensure that they are initialized at load time. + // + // These are used as temporary storage to break the infinite cycle of mmap + // calling our hook which (sometimes) causes mmap. It must be a static + // fixed-size array. The size 20 is just an expected value for safety. + // The details are described in memory_region_map.cc. + + // Number of unprocessed bucket inserts. + static int saved_buckets_count_ GUARDED_BY(lock_); + + // Unprocessed inserts (must be big enough to hold all mmaps that can be + // caused by a GetBucket call). + // Bucket has no constructor, so that c-tor execution does not interfere + // with the any-time use of the static memory behind saved_buckets. + static HeapProfileBucket saved_buckets_[20] GUARDED_BY(lock_); + + static const void* saved_buckets_keys_[20][kMaxStackDepth] GUARDED_BY(lock_); + // helpers ================================================================== // Helper for FindRegion and FindAndMarkStackRegion: @@ -308,6 +356,11 @@ class MemoryRegionMap { // by calling insert_func on them. inline static void HandleSavedRegionsLocked( void (*insert_func)(const Region& region)); + + // Restore buckets saved in a tmp static array by GetBucket to the bucket + // table where all buckets eventually should be. + static void RestoreSavedBucketsLocked(); + // Wrapper around DoInsertRegionLocked // that handles the case of recursive allocator calls. inline static void InsertRegionLocked(const Region& region); @@ -319,6 +372,13 @@ class MemoryRegionMap { // (called from our munmap/mremap/sbrk hooks). static void RecordRegionRemoval(const void* start, size_t size); + // Record deletion of a memory region of size "size" in a bucket whose + // caller stack trace is "key". The stack trace is used to a depth of + // "depth" at most. + static void RecordRegionRemovalInBucket(int depth, + const void* const key[], + size_t size); + // Hooks for MallocHook static void MmapHook(const void* result, const void* start, size_t size, @@ -337,4 +397,16 @@ class MemoryRegionMap { DISALLOW_COPY_AND_ASSIGN(MemoryRegionMap); }; +template <class Type> +void MemoryRegionMap::IterateBuckets( + void (*callback)(const HeapProfileBucket*, Type), Type callback_arg) { + for (int index = 0; index < kHashTableSize; index++) { + for (HeapProfileBucket* bucket = bucket_table_[index]; + bucket != NULL; + bucket = bucket->next) { + callback(bucket, callback_arg); + } + } +} + #endif // BASE_MEMORY_REGION_MAP_H_ |