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