Home | History | Annotate | Download | only in memory_watcher
      1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <algorithm>
      6 #include <windows.h>
      7 #include <tlhelp32.h>     // for CreateToolhelp32Snapshot()
      8 #include <map>
      9 
     10 #include "tools/memory_watcher/memory_watcher.h"
     11 #include "base/file_util.h"
     12 #include "base/logging.h"
     13 #include "base/metrics/stats_counters.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/utf_string_conversions.h"
     16 #include "base/synchronization/lock.h"
     17 #include "tools/memory_watcher/call_stack.h"
     18 #include "tools/memory_watcher/preamble_patcher.h"
     19 
     20 static base::StatsCounter mem_in_use("MemoryInUse.Bytes");
     21 static base::StatsCounter mem_in_use_blocks("MemoryInUse.Blocks");
     22 static base::StatsCounter mem_in_use_allocs("MemoryInUse.Allocs");
     23 static base::StatsCounter mem_in_use_frees("MemoryInUse.Frees");
     24 
     25 // ---------------------------------------------------------------------
     26 
     27 MemoryWatcher::MemoryWatcher()
     28   : file_(NULL),
     29     hooked_(false),
     30     active_thread_id_(0) {
     31   MemoryHook::Initialize();
     32   CallStack::Initialize();
     33 
     34   block_map_ = new CallStackMap();
     35 
     36   // Register last - only after we're ready for notifications!
     37   Hook();
     38 }
     39 
     40 MemoryWatcher::~MemoryWatcher() {
     41   Unhook();
     42 
     43   CloseLogFile();
     44 
     45   // Pointers in the block_map are part of the MemoryHook heap.  Be sure
     46   // to delete the map before closing the heap.
     47   delete block_map_;
     48 }
     49 
     50 void MemoryWatcher::Hook() {
     51   DCHECK(!hooked_);
     52   MemoryHook::RegisterWatcher(this);
     53   hooked_ = true;
     54 }
     55 
     56 void MemoryWatcher::Unhook() {
     57   if (hooked_) {
     58     MemoryHook::UnregisterWatcher(this);
     59     hooked_ = false;
     60   }
     61 }
     62 
     63 void MemoryWatcher::OpenLogFile() {
     64   DCHECK(file_ == NULL);
     65   file_name_ = "memwatcher";
     66   if (!log_name_.empty()) {
     67     file_name_ += ".";
     68     file_name_ += log_name_;
     69   }
     70   file_name_ += ".log";
     71   char buf[16];
     72   file_name_ += _itoa(GetCurrentProcessId(), buf, 10);
     73 
     74   std::string tmp_name(file_name_);
     75   tmp_name += ".tmp";
     76   file_ = fopen(tmp_name.c_str(), "w+");
     77 }
     78 
     79 void MemoryWatcher::CloseLogFile() {
     80   if (file_ != NULL) {
     81     fclose(file_);
     82     file_ = NULL;
     83     std::wstring tmp_name = ASCIIToWide(file_name_);
     84     tmp_name += L".tmp";
     85     base::Move(base::FilePath(tmp_name),
     86                base::FilePath(ASCIIToWide(file_name_)));
     87   }
     88 }
     89 
     90 bool MemoryWatcher::LockedRecursionDetected() const {
     91   if (!active_thread_id_) return false;
     92   DWORD thread_id = GetCurrentThreadId();
     93   // TODO(jar): Perchance we should use atomic access to member.
     94   return thread_id == active_thread_id_;
     95 }
     96 
     97 void MemoryWatcher::OnTrack(HANDLE heap, int32 id, int32 size) {
     98   // Don't track zeroes.  It's a waste of time.
     99   if (size == 0)
    100     return;
    101 
    102   if (LockedRecursionDetected())
    103     return;
    104 
    105   // AllocationStack overrides new/delete to not allocate
    106   // from the main heap.
    107   AllocationStack* stack = new AllocationStack(size);
    108   if (!stack->Valid()) return;  // Recursion blocked generation of stack.
    109 
    110   {
    111     base::AutoLock lock(block_map_lock_);
    112 
    113     // Ideally, we'd like to verify that the block being added
    114     // here is not already in our list of tracked blocks.  However,
    115     // the lookup in our hash table is expensive and slows us too
    116     // much.
    117     CallStackMap::iterator block_it = block_map_->find(id);
    118     if (block_it != block_map_->end()) {
    119 #if 0  // Don't do this until stack->ToString() uses ONLY our heap.
    120       active_thread_id_ = GetCurrentThreadId();
    121       PrivateAllocatorString output;
    122       block_it->second->ToString(&output);
    123      // VLOG(1) << "First Stack size " << stack->size() << "was\n" << output;
    124       stack->ToString(&output);
    125      // VLOG(1) << "Second Stack size " << stack->size() << "was\n" << output;
    126 #endif  // 0
    127 
    128       // TODO(jar): We should delete one stack, and keep the other, perhaps
    129       // based on size.
    130       // For now, just delete the first, and keep the second?
    131       delete block_it->second;
    132     }
    133     // TODO(jar): Perchance we should use atomic access to member.
    134     active_thread_id_ = 0;  // Note: Only do this AFTER exiting above scope!
    135 
    136     (*block_map_)[id] = stack;
    137   }
    138 
    139   mem_in_use.Add(size);
    140   mem_in_use_blocks.Increment();
    141   mem_in_use_allocs.Increment();
    142 }
    143 
    144 void MemoryWatcher::OnUntrack(HANDLE heap, int32 id, int32 size) {
    145   DCHECK_GE(size, 0);
    146 
    147   // Don't bother with these.
    148   if (size == 0)
    149     return;
    150 
    151   if (LockedRecursionDetected())
    152     return;
    153 
    154   {
    155     base::AutoLock lock(block_map_lock_);
    156     active_thread_id_ = GetCurrentThreadId();
    157 
    158     // First, find the block in our block_map.
    159     CallStackMap::iterator it = block_map_->find(id);
    160     if (it != block_map_->end()) {
    161       AllocationStack* stack = it->second;
    162       DCHECK(stack->size() == size);
    163       block_map_->erase(id);
    164       delete stack;
    165     } else {
    166       // Untracked item.  This happens a fair amount, and it is
    167       // normal.  A lot of time elapses during process startup
    168       // before the allocation routines are hooked.
    169       size = 0;  // Ignore size in tallies.
    170     }
    171     // TODO(jar): Perchance we should use atomic access to member.
    172     active_thread_id_ = 0;
    173   }
    174 
    175   mem_in_use.Add(-size);
    176   mem_in_use_blocks.Decrement();
    177   mem_in_use_frees.Increment();
    178 }
    179 
    180 void MemoryWatcher::SetLogName(char* log_name) {
    181   if (!log_name)
    182     return;
    183 
    184   log_name_ = log_name;
    185 }
    186 
    187 // Help sort lists of stacks based on allocation cost.
    188 // Note: Sort based on allocation count is interesting too!
    189 static bool CompareCallStackIdItems(MemoryWatcher::StackTrack* left,
    190                                     MemoryWatcher::StackTrack* right) {
    191   return left->size > right->size;
    192 }
    193 
    194 
    195 void MemoryWatcher::DumpLeaks() {
    196   // We can only dump the leaks once.  We'll cleanup the hooks here.
    197   if (!hooked_)
    198     return;
    199   Unhook();
    200 
    201   base::AutoLock lock(block_map_lock_);
    202   active_thread_id_ = GetCurrentThreadId();
    203 
    204   OpenLogFile();
    205 
    206   // Aggregate contributions from each allocated block on per-stack basis.
    207   CallStackIdMap stack_map;
    208   for (CallStackMap::iterator block_it = block_map_->begin();
    209       block_it != block_map_->end(); ++block_it) {
    210     AllocationStack* stack = block_it->second;
    211     int32 stack_hash = stack->hash();
    212     int32 alloc_block_size = stack->size();
    213     CallStackIdMap::iterator it = stack_map.find(stack_hash);
    214     if (it == stack_map.end()) {
    215       StackTrack tracker;
    216       tracker.count = 1;
    217       tracker.size = alloc_block_size;
    218       tracker.stack = stack;  // Temporary pointer into block_map_.
    219       stack_map[stack_hash] = tracker;
    220     } else {
    221       it->second.count++;
    222       it->second.size += alloc_block_size;
    223     }
    224   }
    225   // Don't release lock yet, as block_map_ is still pointed into.
    226 
    227   // Put references to StrackTracks into array for sorting.
    228   std::vector<StackTrack*, PrivateHookAllocator<int32> >
    229       stack_tracks(stack_map.size());
    230   CallStackIdMap::iterator it = stack_map.begin();
    231   for (size_t i = 0; i < stack_tracks.size(); ++i) {
    232     stack_tracks[i] = &(it->second);
    233     ++it;
    234   }
    235   sort(stack_tracks.begin(), stack_tracks.end(), CompareCallStackIdItems);
    236 
    237   int32 total_bytes = 0;
    238   int32 total_blocks = 0;
    239   for (size_t i = 0; i < stack_tracks.size(); ++i) {
    240     StackTrack* stack_track = stack_tracks[i];
    241     fwprintf(file_, L"%d bytes, %d allocs, #%d\n",
    242              stack_track->size, stack_track->count, i);
    243     total_bytes += stack_track->size;
    244     total_blocks += stack_track->count;
    245 
    246     CallStack* stack = stack_track->stack;
    247     PrivateAllocatorString output;
    248     stack->ToString(&output);
    249     fprintf(file_, "%s", output.c_str());
    250   }
    251   fprintf(file_, "Total Leaks:  %d\n", total_blocks);
    252   fprintf(file_, "Total Stacks: %d\n", stack_tracks.size());
    253   fprintf(file_, "Total Bytes:  %d\n", total_bytes);
    254   CloseLogFile();
    255 }
    256