Home | History | Annotate | Download | only in src
      1 // Copyright (c) 2006, Google Inc.
      2 // All rights reserved.
      3 //
      4 // Redistribution and use in source and binary forms, with or without
      5 // modification, are permitted provided that the following conditions are
      6 // met:
      7 //
      8 //     * Redistributions of source code must retain the above copyright
      9 // notice, this list of conditions and the following disclaimer.
     10 //     * Redistributions in binary form must reproduce the above
     11 // copyright notice, this list of conditions and the following disclaimer
     12 // in the documentation and/or other materials provided with the
     13 // distribution.
     14 //     * Neither the name of Google Inc. nor the names of its
     15 // contributors may be used to endorse or promote products derived from
     16 // this software without specific prior written permission.
     17 //
     18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29 
     30 // ---
     31 // Author: Sanjay Ghemawat
     32 //         Maxim Lifantsev (refactoring)
     33 //
     34 
     35 #include <config.h>
     36 
     37 #ifdef HAVE_UNISTD_H
     38 #include <unistd.h>   // for write()
     39 #endif
     40 #include <fcntl.h>    // for open()
     41 #ifdef HAVE_GLOB_H
     42 #include <glob.h>
     43 #ifndef GLOB_NOMATCH  // true on some old cygwins
     44 # define GLOB_NOMATCH 0
     45 #endif
     46 #endif
     47 #ifdef HAVE_INTTYPES_H
     48 #include <inttypes.h> // for PRIxPTR
     49 #endif
     50 #ifdef HAVE_POLL_H
     51 #include <poll.h>
     52 #endif
     53 #include <errno.h>
     54 #include <stdarg.h>
     55 #include <string>
     56 #include <map>
     57 #include <algorithm>  // for sort(), equal(), and copy()
     58 
     59 #include "heap-profile-table.h"
     60 
     61 #include "base/logging.h"
     62 #include "raw_printer.h"
     63 #include "symbolize.h"
     64 #include <gperftools/stacktrace.h>
     65 #include <gperftools/malloc_hook.h>
     66 #include "memory_region_map.h"
     67 #include "base/commandlineflags.h"
     68 #include "base/logging.h"    // for the RawFD I/O commands
     69 #include "base/sysinfo.h"
     70 
     71 using std::sort;
     72 using std::equal;
     73 using std::copy;
     74 using std::string;
     75 using std::map;
     76 
     77 using tcmalloc::FillProcSelfMaps;   // from sysinfo.h
     78 using tcmalloc::DumpProcSelfMaps;   // from sysinfo.h
     79 
     80 //----------------------------------------------------------------------
     81 
     82 DEFINE_bool(cleanup_old_heap_profiles,
     83             EnvToBool("HEAP_PROFILE_CLEANUP", true),
     84             "At initialization time, delete old heap profiles.");
     85 
     86 DEFINE_int32(heap_check_max_leaks,
     87              EnvToInt("HEAP_CHECK_MAX_LEAKS", 20),
     88              "The maximum number of leak reports to print.");
     89 
     90 //----------------------------------------------------------------------
     91 
     92 // header of the dumped heap profile
     93 static const char kProfileHeader[] = "heap profile: ";
     94 static const char kProcSelfMapsHeader[] = "\nMAPPED_LIBRARIES:\n";
     95 
     96 //----------------------------------------------------------------------
     97 
     98 const char HeapProfileTable::kFileExt[] = ".heap";
     99 
    100 //----------------------------------------------------------------------
    101 
    102 // Size for alloc_table_ and mmap_table_.
    103 static const int kHashTableSize = 179999;
    104 /*static*/ const int HeapProfileTable::kMaxStackDepth;
    105 
    106 //----------------------------------------------------------------------
    107 
    108 // We strip out different number of stack frames in debug mode
    109 // because less inlining happens in that case
    110 #ifdef NDEBUG
    111 static const int kStripFrames = 2;
    112 #else
    113 static const int kStripFrames = 3;
    114 #endif
    115 
    116 // For sorting Stats or Buckets by in-use space
    117 static bool ByAllocatedSpace(HeapProfileTable::Stats* a,
    118                              HeapProfileTable::Stats* b) {
    119   // Return true iff "a" has more allocated space than "b"
    120   return (a->alloc_size - a->free_size) > (b->alloc_size - b->free_size);
    121 }
    122 
    123 //----------------------------------------------------------------------
    124 
    125 HeapProfileTable::HeapProfileTable(Allocator alloc, DeAllocator dealloc)
    126     : alloc_(alloc), dealloc_(dealloc) {
    127   // Initialize the overall profile stats.
    128   memset(&total_, 0, sizeof(total_));
    129 
    130   // Make the malloc table.
    131   const int alloc_table_bytes = kHashTableSize * sizeof(*alloc_table_);
    132   alloc_table_ = reinterpret_cast<Bucket**>(alloc_(alloc_table_bytes));
    133   memset(alloc_table_, 0, alloc_table_bytes);
    134   num_alloc_buckets_ = 0;
    135 
    136   // Initialize the mmap table.
    137   mmap_table_ = NULL;
    138   num_available_mmap_buckets_ = 0;
    139 
    140   // Make malloc and mmap allocation maps.
    141   alloc_address_map_ =
    142       new(alloc_(sizeof(AllocationMap))) AllocationMap(alloc_, dealloc_);
    143   mmap_address_map_ = NULL;
    144 }
    145 
    146 HeapProfileTable::~HeapProfileTable() {
    147   DeallocateBucketTable(alloc_table_);
    148   alloc_table_ = NULL;
    149   DeallocateBucketTable(mmap_table_);
    150   mmap_table_ = NULL;
    151   DeallocateAllocationMap(alloc_address_map_);
    152   alloc_address_map_ = NULL;
    153   DeallocateAllocationMap(mmap_address_map_);
    154   mmap_address_map_ = NULL;
    155 }
    156 
    157 void HeapProfileTable::DeallocateAllocationMap(AllocationMap* allocation) {
    158   if (allocation != NULL) {
    159     alloc_address_map_->~AllocationMap();
    160     dealloc_(allocation);
    161   }
    162 }
    163 
    164 void HeapProfileTable::DeallocateBucketTable(Bucket** table) {
    165   if (table != NULL) {
    166     for (int b = 0; b < kHashTableSize; b++) {
    167       for (Bucket* x = table[b]; x != 0; /**/) {
    168         Bucket* b = x;
    169         x = x->next;
    170         dealloc_(b->stack);
    171         dealloc_(b);
    172       }
    173     }
    174     dealloc_(table);
    175   }
    176 }
    177 
    178 HeapProfileTable::Bucket* HeapProfileTable::GetBucket(
    179     int depth, const void* const key[], Bucket** table,
    180     int* bucket_count) {
    181   // Make hash-value
    182   uintptr_t h = 0;
    183   for (int i = 0; i < depth; i++) {
    184     h += reinterpret_cast<uintptr_t>(key[i]);
    185     h += h << 10;
    186     h ^= h >> 6;
    187   }
    188   h += h << 3;
    189   h ^= h >> 11;
    190 
    191   // Lookup stack trace in table
    192   unsigned int buck = ((unsigned int) h) % kHashTableSize;
    193   for (Bucket* b = table[buck]; b != 0; b = b->next) {
    194     if ((b->hash == h) &&
    195         (b->depth == depth) &&
    196         equal(key, key + depth, b->stack)) {
    197       return b;
    198     }
    199   }
    200 
    201   // Create new bucket
    202   const size_t key_size = sizeof(key[0]) * depth;
    203   const void** kcopy = reinterpret_cast<const void**>(alloc_(key_size));
    204   copy(key, key + depth, kcopy);
    205   Bucket* b = reinterpret_cast<Bucket*>(alloc_(sizeof(Bucket)));
    206   memset(b, 0, sizeof(*b));
    207   b->hash  = h;
    208   b->depth = depth;
    209   b->stack = kcopy;
    210   b->next  = table[buck];
    211   table[buck] = b;
    212   if (bucket_count != NULL) {
    213     ++(*bucket_count);
    214   }
    215   return b;
    216 }
    217 
    218 int HeapProfileTable::GetCallerStackTrace(
    219     int skip_count, void* stack[kMaxStackDepth]) {
    220   return MallocHook::GetCallerStackTrace(
    221       stack, kMaxStackDepth, kStripFrames + skip_count + 1);
    222 }
    223 
    224 void HeapProfileTable::RecordAlloc(
    225     const void* ptr, size_t bytes, int stack_depth,
    226     const void* const call_stack[]) {
    227   Bucket* b = GetBucket(stack_depth, call_stack, alloc_table_,
    228                         &num_alloc_buckets_);
    229   b->allocs++;
    230   b->alloc_size += bytes;
    231   total_.allocs++;
    232   total_.alloc_size += bytes;
    233 
    234   AllocValue v;
    235   v.set_bucket(b);  // also did set_live(false); set_ignore(false)
    236   v.bytes = bytes;
    237   alloc_address_map_->Insert(ptr, v);
    238 }
    239 
    240 void HeapProfileTable::RecordFree(const void* ptr) {
    241   AllocValue v;
    242   if (alloc_address_map_->FindAndRemove(ptr, &v)) {
    243     Bucket* b = v.bucket();
    244     b->frees++;
    245     b->free_size += v.bytes;
    246     total_.frees++;
    247     total_.free_size += v.bytes;
    248   }
    249 }
    250 
    251 bool HeapProfileTable::FindAlloc(const void* ptr, size_t* object_size) const {
    252   const AllocValue* alloc_value = alloc_address_map_->Find(ptr);
    253   if (alloc_value != NULL) *object_size = alloc_value->bytes;
    254   return alloc_value != NULL;
    255 }
    256 
    257 bool HeapProfileTable::FindAllocDetails(const void* ptr,
    258                                         AllocInfo* info) const {
    259   const AllocValue* alloc_value = alloc_address_map_->Find(ptr);
    260   if (alloc_value != NULL) {
    261     info->object_size = alloc_value->bytes;
    262     info->call_stack = alloc_value->bucket()->stack;
    263     info->stack_depth = alloc_value->bucket()->depth;
    264   }
    265   return alloc_value != NULL;
    266 }
    267 
    268 bool HeapProfileTable::FindInsideAlloc(const void* ptr,
    269                                        size_t max_size,
    270                                        const void** object_ptr,
    271                                        size_t* object_size) const {
    272   const AllocValue* alloc_value =
    273     alloc_address_map_->FindInside(&AllocValueSize, max_size, ptr, object_ptr);
    274   if (alloc_value != NULL) *object_size = alloc_value->bytes;
    275   return alloc_value != NULL;
    276 }
    277 
    278 bool HeapProfileTable::MarkAsLive(const void* ptr) {
    279   AllocValue* alloc = alloc_address_map_->FindMutable(ptr);
    280   if (alloc && !alloc->live()) {
    281     alloc->set_live(true);
    282     return true;
    283   }
    284   return false;
    285 }
    286 
    287 void HeapProfileTable::MarkAsIgnored(const void* ptr) {
    288   AllocValue* alloc = alloc_address_map_->FindMutable(ptr);
    289   if (alloc) {
    290     alloc->set_ignore(true);
    291   }
    292 }
    293 
    294 // We'd be happier using snprintfer, but we don't to reduce dependencies.
    295 int HeapProfileTable::UnparseBucket(const Bucket& b,
    296                                     char* buf, int buflen, int bufsize,
    297                                     const char* extra,
    298                                     Stats* profile_stats) {
    299   if (profile_stats != NULL) {
    300     profile_stats->allocs += b.allocs;
    301     profile_stats->alloc_size += b.alloc_size;
    302     profile_stats->frees += b.frees;
    303     profile_stats->free_size += b.free_size;
    304   }
    305   int printed =
    306     snprintf(buf + buflen, bufsize - buflen, "%6d: %8"PRId64" [%6d: %8"PRId64"] @%s",
    307              b.allocs - b.frees,
    308              b.alloc_size - b.free_size,
    309              b.allocs,
    310              b.alloc_size,
    311              extra);
    312   // If it looks like the snprintf failed, ignore the fact we printed anything
    313   if (printed < 0 || printed >= bufsize - buflen) return buflen;
    314   buflen += printed;
    315   for (int d = 0; d < b.depth; d++) {
    316     printed = snprintf(buf + buflen, bufsize - buflen, " 0x%08" PRIxPTR,
    317                        reinterpret_cast<uintptr_t>(b.stack[d]));
    318     if (printed < 0 || printed >= bufsize - buflen) return buflen;
    319     buflen += printed;
    320   }
    321   printed = snprintf(buf + buflen, bufsize - buflen, "\n");
    322   if (printed < 0 || printed >= bufsize - buflen) return buflen;
    323   buflen += printed;
    324   return buflen;
    325 }
    326 
    327 HeapProfileTable::Bucket**
    328 HeapProfileTable::MakeSortedBucketList() const {
    329   Bucket** list = reinterpret_cast<Bucket**>(alloc_(sizeof(Bucket) *
    330       (num_alloc_buckets_ + num_available_mmap_buckets_)));
    331 
    332   RAW_DCHECK(mmap_table_ != NULL || num_available_mmap_buckets_ == 0, "");
    333 
    334   int n = 0;
    335 
    336   for (int b = 0; b < kHashTableSize; b++) {
    337     for (Bucket* x = alloc_table_[b]; x != 0; x = x->next) {
    338       list[n++] = x;
    339     }
    340   }
    341   RAW_DCHECK(n == num_alloc_buckets_, "");
    342 
    343   if (mmap_table_ != NULL) {
    344     for (int b = 0; b < kHashTableSize; b++) {
    345       for (Bucket* x = mmap_table_[b]; x != 0; x = x->next) {
    346         list[n++] = x;
    347       }
    348     }
    349   }
    350   RAW_DCHECK(n == num_alloc_buckets_ + num_available_mmap_buckets_, "");
    351 
    352   sort(list, list + num_alloc_buckets_ + num_available_mmap_buckets_,
    353        ByAllocatedSpace);
    354 
    355   return list;
    356 }
    357 
    358 void HeapProfileTable::RefreshMMapData() {
    359   // Make the table
    360   static const int mmap_table_bytes = kHashTableSize * sizeof(*mmap_table_);
    361   if (mmap_table_ == NULL) {
    362     mmap_table_ = reinterpret_cast<Bucket**>(alloc_(mmap_table_bytes));
    363     memset(mmap_table_, 0, mmap_table_bytes);
    364   }
    365   num_available_mmap_buckets_ = 0;
    366 
    367   ClearMMapData();
    368   mmap_address_map_ =
    369       new(alloc_(sizeof(AllocationMap))) AllocationMap(alloc_, dealloc_);
    370 
    371   MemoryRegionMap::LockHolder l;
    372   for (MemoryRegionMap::RegionIterator r =
    373            MemoryRegionMap::BeginRegionLocked();
    374        r != MemoryRegionMap::EndRegionLocked(); ++r) {
    375     Bucket* b =
    376         GetBucket(r->call_stack_depth, r->call_stack, mmap_table_, NULL);
    377     if (b->alloc_size == 0) {
    378       num_available_mmap_buckets_ += 1;
    379     }
    380     b->allocs += 1;
    381     b->alloc_size += r->end_addr - r->start_addr;
    382 
    383     AllocValue v;
    384     v.set_bucket(b);
    385     v.bytes = r->end_addr - r->start_addr;
    386     mmap_address_map_->Insert(reinterpret_cast<const void*>(r->start_addr), v);
    387   }
    388 }
    389 
    390 void HeapProfileTable::ClearMMapData() {
    391   if (mmap_address_map_ != NULL) {
    392     mmap_address_map_->Iterate(ZeroBucketCountsIterator, this);
    393     mmap_address_map_->~AllocationMap();
    394     dealloc_(mmap_address_map_);
    395     mmap_address_map_ = NULL;
    396   }
    397 }
    398 
    399 void HeapProfileTable::IterateOrderedAllocContexts(
    400     AllocContextIterator callback) const {
    401   Bucket** list = MakeSortedBucketList();
    402   AllocContextInfo info;
    403   for (int i = 0; i < num_alloc_buckets_; ++i) {
    404     *static_cast<Stats*>(&info) = *static_cast<Stats*>(list[i]);
    405     info.stack_depth = list[i]->depth;
    406     info.call_stack = list[i]->stack;
    407     callback(info);
    408   }
    409   dealloc_(list);
    410 }
    411 
    412 int HeapProfileTable::FillOrderedProfile(char buf[], int size) const {
    413   Bucket** list = MakeSortedBucketList();
    414 
    415   // Our file format is "bucket, bucket, ..., bucket, proc_self_maps_info".
    416   // In the cases buf is too small, we'd rather leave out the last
    417   // buckets than leave out the /proc/self/maps info.  To ensure that,
    418   // we actually print the /proc/self/maps info first, then move it to
    419   // the end of the buffer, then write the bucket info into whatever
    420   // is remaining, and then move the maps info one last time to close
    421   // any gaps.  Whew!
    422   int map_length = snprintf(buf, size, "%s", kProcSelfMapsHeader);
    423   if (map_length < 0 || map_length >= size) return 0;
    424   bool dummy;   // "wrote_all" -- did /proc/self/maps fit in its entirety?
    425   map_length += FillProcSelfMaps(buf + map_length, size - map_length, &dummy);
    426   RAW_DCHECK(map_length <= size, "");
    427   char* const map_start = buf + size - map_length;      // move to end
    428   memmove(map_start, buf, map_length);
    429   size -= map_length;
    430 
    431   Stats stats;
    432   memset(&stats, 0, sizeof(stats));
    433   int bucket_length = snprintf(buf, size, "%s", kProfileHeader);
    434   if (bucket_length < 0 || bucket_length >= size) return 0;
    435   Bucket total_with_mmap(total_);
    436   if (mmap_table_ != NULL) {
    437     total_with_mmap.alloc_size += MemoryRegionMap::MapSize();
    438     total_with_mmap.free_size += MemoryRegionMap::UnmapSize();
    439   }
    440   bucket_length = UnparseBucket(total_with_mmap, buf, bucket_length, size,
    441                                 " heapprofile", &stats);
    442   for (int i = 0; i < num_alloc_buckets_; i++) {
    443     bucket_length = UnparseBucket(*list[i], buf, bucket_length, size, "",
    444                                   &stats);
    445   }
    446   RAW_DCHECK(bucket_length < size, "");
    447 
    448   dealloc_(list);
    449 
    450   RAW_DCHECK(buf + bucket_length <= map_start, "");
    451   memmove(buf + bucket_length, map_start, map_length);  // close the gap
    452 
    453   return bucket_length + map_length;
    454 }
    455 
    456 inline
    457 void HeapProfileTable::DumpNonLiveIterator(const void* ptr, AllocValue* v,
    458                                            const DumpArgs& args) {
    459   if (v->live()) {
    460     v->set_live(false);
    461     return;
    462   }
    463   if (v->ignore()) {
    464     return;
    465   }
    466   Bucket b;
    467   memset(&b, 0, sizeof(b));
    468   b.allocs = 1;
    469   b.alloc_size = v->bytes;
    470   b.depth = v->bucket()->depth;
    471   b.stack = v->bucket()->stack;
    472   char buf[1024];
    473   int len = UnparseBucket(b, buf, 0, sizeof(buf), "", args.profile_stats);
    474   RawWrite(args.fd, buf, len);
    475 }
    476 
    477 inline void HeapProfileTable::ZeroBucketCountsIterator(
    478     const void* ptr, AllocValue* v, HeapProfileTable* heap_profile) {
    479   Bucket* b = v->bucket();
    480   if (b != NULL) {
    481     b->allocs = 0;
    482     b->alloc_size = 0;
    483     b->free_size = 0;
    484     b->frees = 0;
    485   }
    486 }
    487 
    488 // Callback from NonLiveSnapshot; adds entry to arg->dest
    489 // if not the entry is not live and is not present in arg->base.
    490 void HeapProfileTable::AddIfNonLive(const void* ptr, AllocValue* v,
    491                                     AddNonLiveArgs* arg) {
    492   if (v->live()) {
    493     v->set_live(false);
    494   } else {
    495     if (arg->base != NULL && arg->base->map_.Find(ptr) != NULL) {
    496       // Present in arg->base, so do not save
    497     } else {
    498       arg->dest->Add(ptr, *v);
    499     }
    500   }
    501 }
    502 
    503 bool HeapProfileTable::WriteProfile(const char* file_name,
    504                                     const Bucket& total,
    505                                     AllocationMap* allocations) {
    506   RAW_VLOG(1, "Dumping non-live heap profile to %s", file_name);
    507   RawFD fd = RawOpenForWriting(file_name);
    508   if (fd != kIllegalRawFD) {
    509     RawWrite(fd, kProfileHeader, strlen(kProfileHeader));
    510     char buf[512];
    511     int len = UnparseBucket(total, buf, 0, sizeof(buf), " heapprofile",
    512                             NULL);
    513     RawWrite(fd, buf, len);
    514     const DumpArgs args(fd, NULL);
    515     allocations->Iterate<const DumpArgs&>(DumpNonLiveIterator, args);
    516     RawWrite(fd, kProcSelfMapsHeader, strlen(kProcSelfMapsHeader));
    517     DumpProcSelfMaps(fd);
    518     RawClose(fd);
    519     return true;
    520   } else {
    521     RAW_LOG(ERROR, "Failed dumping filtered heap profile to %s", file_name);
    522     return false;
    523   }
    524 }
    525 
    526 void HeapProfileTable::CleanupOldProfiles(const char* prefix) {
    527   if (!FLAGS_cleanup_old_heap_profiles)
    528     return;
    529   string pattern = string(prefix) + ".*" + kFileExt;
    530 #if defined(HAVE_GLOB_H)
    531   glob_t g;
    532   const int r = glob(pattern.c_str(), GLOB_ERR, NULL, &g);
    533   if (r == 0 || r == GLOB_NOMATCH) {
    534     const int prefix_length = strlen(prefix);
    535     for (int i = 0; i < g.gl_pathc; i++) {
    536       const char* fname = g.gl_pathv[i];
    537       if ((strlen(fname) >= prefix_length) &&
    538           (memcmp(fname, prefix, prefix_length) == 0)) {
    539         RAW_VLOG(1, "Removing old heap profile %s", fname);
    540         unlink(fname);
    541       }
    542     }
    543   }
    544   globfree(&g);
    545 #else   /* HAVE_GLOB_H */
    546   RAW_LOG(WARNING, "Unable to remove old heap profiles (can't run glob())");
    547 #endif
    548 }
    549 
    550 HeapProfileTable::Snapshot* HeapProfileTable::TakeSnapshot() {
    551   Snapshot* s = new (alloc_(sizeof(Snapshot))) Snapshot(alloc_, dealloc_);
    552   alloc_address_map_->Iterate(AddToSnapshot, s);
    553   return s;
    554 }
    555 
    556 void HeapProfileTable::ReleaseSnapshot(Snapshot* s) {
    557   s->~Snapshot();
    558   dealloc_(s);
    559 }
    560 
    561 // Callback from TakeSnapshot; adds a single entry to snapshot
    562 void HeapProfileTable::AddToSnapshot(const void* ptr, AllocValue* v,
    563                                      Snapshot* snapshot) {
    564   snapshot->Add(ptr, *v);
    565 }
    566 
    567 HeapProfileTable::Snapshot* HeapProfileTable::NonLiveSnapshot(
    568     Snapshot* base) {
    569   RAW_VLOG(2, "NonLiveSnapshot input: %d %d\n",
    570            int(total_.allocs - total_.frees),
    571            int(total_.alloc_size - total_.free_size));
    572 
    573   Snapshot* s = new (alloc_(sizeof(Snapshot))) Snapshot(alloc_, dealloc_);
    574   AddNonLiveArgs args;
    575   args.dest = s;
    576   args.base = base;
    577   alloc_address_map_->Iterate<AddNonLiveArgs*>(AddIfNonLive, &args);
    578   RAW_VLOG(2, "NonLiveSnapshot output: %d %d\n",
    579            int(s->total_.allocs - s->total_.frees),
    580            int(s->total_.alloc_size - s->total_.free_size));
    581   return s;
    582 }
    583 
    584 // Information kept per unique bucket seen
    585 struct HeapProfileTable::Snapshot::Entry {
    586   int count;
    587   int bytes;
    588   Bucket* bucket;
    589   Entry() : count(0), bytes(0) { }
    590 
    591   // Order by decreasing bytes
    592   bool operator<(const Entry& x) const {
    593     return this->bytes > x.bytes;
    594   }
    595 };
    596 
    597 // State used to generate leak report.  We keep a mapping from Bucket pointer
    598 // the collected stats for that bucket.
    599 struct HeapProfileTable::Snapshot::ReportState {
    600   map<Bucket*, Entry> buckets_;
    601 };
    602 
    603 // Callback from ReportLeaks; updates ReportState.
    604 void HeapProfileTable::Snapshot::ReportCallback(const void* ptr,
    605                                                 AllocValue* v,
    606                                                 ReportState* state) {
    607   Entry* e = &state->buckets_[v->bucket()]; // Creates empty Entry first time
    608   e->bucket = v->bucket();
    609   e->count++;
    610   e->bytes += v->bytes;
    611 }
    612 
    613 void HeapProfileTable::Snapshot::ReportLeaks(const char* checker_name,
    614                                              const char* filename,
    615                                              bool should_symbolize) {
    616   // This is only used by the heap leak checker, but is intimately
    617   // tied to the allocation map that belongs in this module and is
    618   // therefore placed here.
    619   RAW_LOG(ERROR, "Leak check %s detected leaks of %"PRIuS" bytes "
    620           "in %"PRIuS" objects",
    621           checker_name,
    622           size_t(total_.alloc_size),
    623           size_t(total_.allocs));
    624 
    625   // Group objects by Bucket
    626   ReportState state;
    627   map_.Iterate(&ReportCallback, &state);
    628 
    629   // Sort buckets by decreasing leaked size
    630   const int n = state.buckets_.size();
    631   Entry* entries = new Entry[n];
    632   int dst = 0;
    633   for (map<Bucket*,Entry>::const_iterator iter = state.buckets_.begin();
    634        iter != state.buckets_.end();
    635        ++iter) {
    636     entries[dst++] = iter->second;
    637   }
    638   sort(entries, entries + n);
    639 
    640   // Report a bounded number of leaks to keep the leak report from
    641   // growing too long.
    642   const int to_report =
    643       (FLAGS_heap_check_max_leaks > 0 &&
    644        n > FLAGS_heap_check_max_leaks) ? FLAGS_heap_check_max_leaks : n;
    645   RAW_LOG(ERROR, "The %d largest leaks:", to_report);
    646 
    647   // Print
    648   SymbolTable symbolization_table;
    649   for (int i = 0; i < to_report; i++) {
    650     const Entry& e = entries[i];
    651     for (int j = 0; j < e.bucket->depth; j++) {
    652       symbolization_table.Add(e.bucket->stack[j]);
    653     }
    654   }
    655   static const int kBufSize = 2<<10;
    656   char buffer[kBufSize];
    657   if (should_symbolize)
    658     symbolization_table.Symbolize();
    659   for (int i = 0; i < to_report; i++) {
    660     const Entry& e = entries[i];
    661     base::RawPrinter printer(buffer, kBufSize);
    662     printer.Printf("Leak of %d bytes in %d objects allocated from:\n",
    663                    e.bytes, e.count);
    664     for (int j = 0; j < e.bucket->depth; j++) {
    665       const void* pc = e.bucket->stack[j];
    666       printer.Printf("\t@ %"PRIxPTR" %s\n",
    667           reinterpret_cast<uintptr_t>(pc), symbolization_table.GetSymbol(pc));
    668     }
    669     RAW_LOG(ERROR, "%s", buffer);
    670   }
    671 
    672   if (to_report < n) {
    673     RAW_LOG(ERROR, "Skipping leaks numbered %d..%d",
    674             to_report, n-1);
    675   }
    676   delete[] entries;
    677 
    678   // TODO: Dump the sorted Entry list instead of dumping raw data?
    679   // (should be much shorter)
    680   if (!HeapProfileTable::WriteProfile(filename, total_, &map_)) {
    681     RAW_LOG(ERROR, "Could not write pprof profile to %s", filename);
    682   }
    683 }
    684 
    685 void HeapProfileTable::Snapshot::ReportObject(const void* ptr,
    686                                               AllocValue* v,
    687                                               char* unused) {
    688   // Perhaps also log the allocation stack trace (unsymbolized)
    689   // on this line in case somebody finds it useful.
    690   RAW_LOG(ERROR, "leaked %"PRIuS" byte object %p", v->bytes, ptr);
    691 }
    692 
    693 void HeapProfileTable::Snapshot::ReportIndividualObjects() {
    694   char unused;
    695   map_.Iterate(ReportObject, &unused);
    696 }
    697