blob: 77faa8b1fab224ec37d472312fea12f6408f8781 [file] [log] [blame]
[email protected]90d890d2010-10-20 00:11:341// Copyright (c) 2010 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit920c0912008-07-27 00:12:164
[email protected]39a749c2011-01-28 02:40:465#include <algorithm>
initial.commit920c0912008-07-27 00:12:166#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"
initial.commit920c0912008-07-27 00:12:1612#include "base/logging.h"
[email protected]835d7c82010-10-14 04:38:3813#include "base/metrics/stats_counters.h"
initial.commit920c0912008-07-27 00:12:1614#include "base/string_util.h"
[email protected]20305ec2011-01-21 04:55:5215#include "base/synchronization/lock.h"
[email protected]be1ce6a72010-08-03 14:35:2216#include "base/utf_string_conversions.h"
initial.commit920c0912008-07-27 00:12:1617#include "tools/memory_watcher/call_stack.h"
18#include "tools/memory_watcher/preamble_patcher.h"
19
[email protected]835d7c82010-10-14 04:38:3820static base::StatsCounter mem_in_use("MemoryInUse.Bytes");
21static base::StatsCounter mem_in_use_blocks("MemoryInUse.Blocks");
22static base::StatsCounter mem_in_use_allocs("MemoryInUse.Allocs");
23static base::StatsCounter mem_in_use_frees("MemoryInUse.Frees");
initial.commit920c0912008-07-27 00:12:1624
25// ---------------------------------------------------------------------
26
27MemoryWatcher::MemoryWatcher()
28 : file_(NULL),
29 hooked_(false),
[email protected]0c685452009-11-06 21:17:5130 active_thread_id_(0) {
initial.commit920c0912008-07-27 00:12:1631 MemoryHook::Initialize();
32 CallStack::Initialize();
33
34 block_map_ = new CallStackMap();
initial.commit920c0912008-07-27 00:12:1635
36 // Register last - only after we're ready for notifications!
37 Hook();
38}
39
40MemoryWatcher::~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
50void MemoryWatcher::Hook() {
51 DCHECK(!hooked_);
52 MemoryHook::RegisterWatcher(this);
53 hooked_ = true;
54}
55
56void MemoryWatcher::Unhook() {
57 if (hooked_) {
58 MemoryHook::UnregisterWatcher(this);
59 hooked_ = false;
60 }
61}
62
63void 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
79void 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";
[email protected]0da011cf2011-03-15 19:23:1485 file_util::Move(FilePath(tmp_name), FilePath(ASCIIToWide(file_name_)));
initial.commit920c0912008-07-27 00:12:1686 }
87}
88
[email protected]0c685452009-11-06 21:17:5189bool MemoryWatcher::LockedRecursionDetected() const {
90 if (!active_thread_id_) return false;
91 DWORD thread_id = GetCurrentThreadId();
92 // TODO(jar): Perchance we should use atomic access to member.
93 return thread_id == active_thread_id_;
94}
95
initial.commit920c0912008-07-27 00:12:1696void MemoryWatcher::OnTrack(HANDLE heap, int32 id, int32 size) {
[email protected]3c4aa42a2009-10-31 21:59:3097 // Don't track zeroes. It's a waste of time.
98 if (size == 0)
99 return;
100
[email protected]0c685452009-11-06 21:17:51101 if (LockedRecursionDetected())
102 return;
103
initial.commit920c0912008-07-27 00:12:16104 // AllocationStack overrides new/delete to not allocate
105 // from the main heap.
[email protected]0c685452009-11-06 21:17:51106 AllocationStack* stack = new AllocationStack(size);
107 if (!stack->Valid()) return; // Recursion blocked generation of stack.
108
initial.commit920c0912008-07-27 00:12:16109 {
[email protected]20305ec2011-01-21 04:55:52110 base::AutoLock lock(block_map_lock_);
initial.commit920c0912008-07-27 00:12:16111
[email protected]f0a51fb52009-03-05 12:46:38112 // Ideally, we'd like to verify that the block being added
initial.commit920c0912008-07-27 00:12:16113 // here is not already in our list of tracked blocks. However,
114 // the lookup in our hash table is expensive and slows us too
[email protected]0c685452009-11-06 21:17:51115 // much.
116 CallStackMap::iterator block_it = block_map_->find(id);
117 if (block_it != block_map_->end()) {
118#if 0 // Don't do this until stack->ToString() uses ONLY our heap.
119 active_thread_id_ = GetCurrentThreadId();
120 PrivateAllocatorString output;
121 block_it->second->ToString(&output);
[email protected]90d890d2010-10-20 00:11:34122 // VLOG(1) << "First Stack size " << stack->size() << "was\n" << output;
[email protected]0c685452009-11-06 21:17:51123 stack->ToString(&output);
[email protected]90d890d2010-10-20 00:11:34124 // VLOG(1) << "Second Stack size " << stack->size() << "was\n" << output;
[email protected]0c685452009-11-06 21:17:51125#endif // 0
126
127 // TODO(jar): We should delete one stack, and keep the other, perhaps
128 // based on size.
129 // For now, just delete the first, and keep the second?
130 delete block_it->second;
131 }
132 // TODO(jar): Perchance we should use atomic access to member.
133 active_thread_id_ = 0; // Note: Only do this AFTER exiting above scope!
initial.commit920c0912008-07-27 00:12:16134
135 (*block_map_)[id] = stack;
initial.commit920c0912008-07-27 00:12:16136 }
137
[email protected]0c685452009-11-06 21:17:51138 mem_in_use.Add(size);
initial.commit920c0912008-07-27 00:12:16139 mem_in_use_blocks.Increment();
140 mem_in_use_allocs.Increment();
141}
142
143void MemoryWatcher::OnUntrack(HANDLE heap, int32 id, int32 size) {
[email protected]3c4aa42a2009-10-31 21:59:30144 DCHECK_GE(size, 0);
initial.commit920c0912008-07-27 00:12:16145
146 // Don't bother with these.
147 if (size == 0)
148 return;
149
[email protected]0c685452009-11-06 21:17:51150 if (LockedRecursionDetected())
151 return;
152
initial.commit920c0912008-07-27 00:12:16153 {
[email protected]20305ec2011-01-21 04:55:52154 base::AutoLock lock(block_map_lock_);
[email protected]0c685452009-11-06 21:17:51155 active_thread_id_ = GetCurrentThreadId();
initial.commit920c0912008-07-27 00:12:16156
157 // First, find the block in our block_map.
158 CallStackMap::iterator it = block_map_->find(id);
159 if (it != block_map_->end()) {
160 AllocationStack* stack = it->second;
[email protected]0c685452009-11-06 21:17:51161 DCHECK(stack->size() == size);
initial.commit920c0912008-07-27 00:12:16162 block_map_->erase(id);
[email protected]0c685452009-11-06 21:17:51163 delete stack;
initial.commit920c0912008-07-27 00:12:16164 } else {
165 // Untracked item. This happens a fair amount, and it is
166 // normal. A lot of time elapses during process startup
167 // before the allocation routines are hooked.
[email protected]0c685452009-11-06 21:17:51168 size = 0; // Ignore size in tallies.
initial.commit920c0912008-07-27 00:12:16169 }
[email protected]0c685452009-11-06 21:17:51170 // TODO(jar): Perchance we should use atomic access to member.
171 active_thread_id_ = 0;
initial.commit920c0912008-07-27 00:12:16172 }
173
[email protected]0c685452009-11-06 21:17:51174 mem_in_use.Add(-size);
initial.commit920c0912008-07-27 00:12:16175 mem_in_use_blocks.Decrement();
176 mem_in_use_frees.Increment();
177}
178
179void MemoryWatcher::SetLogName(char* log_name) {
180 if (!log_name)
181 return;
[email protected]f0a51fb52009-03-05 12:46:38182
initial.commit920c0912008-07-27 00:12:16183 log_name_ = log_name;
184}
185
[email protected]0c685452009-11-06 21:17:51186// Help sort lists of stacks based on allocation cost.
187// Note: Sort based on allocation count is interesting too!
188static bool CompareCallStackIdItems(MemoryWatcher::StackTrack* left,
189 MemoryWatcher::StackTrack* right) {
190 return left->size > right->size;
191}
192
193
initial.commit920c0912008-07-27 00:12:16194void MemoryWatcher::DumpLeaks() {
195 // We can only dump the leaks once. We'll cleanup the hooks here.
[email protected]0c685452009-11-06 21:17:51196 if (!hooked_)
197 return;
initial.commit920c0912008-07-27 00:12:16198 Unhook();
199
[email protected]20305ec2011-01-21 04:55:52200 base::AutoLock lock(block_map_lock_);
[email protected]0c685452009-11-06 21:17:51201 active_thread_id_ = GetCurrentThreadId();
initial.commit920c0912008-07-27 00:12:16202
203 OpenLogFile();
204
[email protected]0c685452009-11-06 21:17:51205 // Aggregate contributions from each allocated block on per-stack basis.
206 CallStackIdMap stack_map;
207 for (CallStackMap::iterator block_it = block_map_->begin();
208 block_it != block_map_->end(); ++block_it) {
209 AllocationStack* stack = block_it->second;
210 int32 stack_hash = stack->hash();
211 int32 alloc_block_size = stack->size();
212 CallStackIdMap::iterator it = stack_map.find(stack_hash);
213 if (it == stack_map.end()) {
214 StackTrack tracker;
215 tracker.count = 1;
216 tracker.size = alloc_block_size;
217 tracker.stack = stack; // Temporary pointer into block_map_.
218 stack_map[stack_hash] = tracker;
219 } else {
220 it->second.count++;
221 it->second.size += alloc_block_size;
222 }
223 }
224 // Don't release lock yet, as block_map_ is still pointed into.
225
226 // Put references to StrackTracks into array for sorting.
227 std::vector<StackTrack*, PrivateHookAllocator<int32> >
228 stack_tracks(stack_map.size());
229 CallStackIdMap::iterator it = stack_map.begin();
230 for (size_t i = 0; i < stack_tracks.size(); ++i) {
231 stack_tracks[i] = &(it->second);
232 ++it;
233 }
234 sort(stack_tracks.begin(), stack_tracks.end(), CompareCallStackIdItems);
235
236 int32 total_bytes = 0;
237 int32 total_blocks = 0;
238 for (size_t i = 0; i < stack_tracks.size(); ++i) {
239 StackTrack* stack_track = stack_tracks[i];
240 fwprintf(file_, L"%d bytes, %d allocs, #%d\n",
241 stack_track->size, stack_track->count, i);
242 total_bytes += stack_track->size;
243 total_blocks += stack_track->count;
244
245 CallStack* stack = stack_track->stack;
246 PrivateAllocatorString output;
initial.commit920c0912008-07-27 00:12:16247 stack->ToString(&output);
248 fprintf(file_, "%s", output.c_str());
initial.commit920c0912008-07-27 00:12:16249 }
[email protected]0c685452009-11-06 21:17:51250 fprintf(file_, "Total Leaks: %d\n", total_blocks);
251 fprintf(file_, "Total Stacks: %d\n", stack_tracks.size());
252 fprintf(file_, "Total Bytes: %d\n", total_bytes);
initial.commit920c0912008-07-27 00:12:16253 CloseLogFile();
254}