Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(179)

Side by Side Diff: components/metrics/leak_detector/leak_detector_impl.cc

Issue 986503002: components/metrics: Add runtime memory leak detector (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix style, comments, RAW_CHECK in stl_allocator.h Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 "leak_detector_impl.h"
6
7 #include <inttypes.h>
8 #include <stddef.h>
9 #include <stdint.h>
10
11 #include <algorithm>
12 #include <new>
13
14 #include "base/hash.h"
15 #include "components/metrics/leak_detector/call_stack_table.h"
16 #include "components/metrics/leak_detector/ranked_list.h"
17
18 namespace leak_detector {
19
20 namespace {
21
22 // Look for leaks in the the top N entries in each tier, where N is this value.
23 const int kRankedListSize = 16;
24
25 // Initial hash table size for |LeakDetectorImpl::address_map_|.
26 const int kAddressMapNumBuckets = 100003;
27
28 // Number of entries in the alloc size table. As sizes are aligned to 32-bits
29 // the max supported allocation size is (kNumSizeEntries * 4 - 1). Any larger
30 // sizes are ignored. This value is chosen high enough that such large sizes
31 // are rare if not nonexistent.
32 const int kNumSizeEntries = 2048;
33
34 using ValueType = LeakDetectorValueType;
35
36 // Prints the input string buffer using RAW_LOG, pre-fixing each line with the
37 // process id.
38 void PrintWithPidOnEachLine(char* buf) {
39 char *ptr = strchr(buf, '\n');
40 do {
41 // Break up the string.
42 if (ptr)
43 *ptr = '\0';
44 // Print out the former part.
45 char line[1024];
46 snprintf(line, sizeof(line), "%d: %s\n", getpid(), buf);
47 RAW_LOG(ERROR, line);
48
49 // Re-point |buf| to the latter part.
50 if (ptr)
51 buf = ptr + 1;
jar (doing other things) 2015/08/21 02:48:43 nit: Perhaps it would be cleaner to not mutate the
Simon Que 2015/08/21 21:59:20 Done.
52 ptr = strchr(buf, '\n');
jar (doing other things) 2015/08/21 02:48:43 It looks like you won't print the final segment of
Simon Que 2015/08/21 21:59:20 Done.
53 } while (ptr);
54 }
55
56 // Functions to convert an allocation size to/from the array index used for
57 // |LeakDetectorImpl::size_entries_|.
58 int SizeToIndex(size_t size){
59 int result = static_cast<int>(size /= sizeof(uint32_t));
jar (doing other things) 2015/08/21 02:48:43 nit: I suspect you didn't mean to use "/=". You d
Simon Que 2015/08/21 21:59:20 Done.
60 if (result < kNumSizeEntries)
61 return result;
62 return 0;
63 }
64
65 size_t IndexToSize(int index){
66 return sizeof(uint32_t) * index;
67 }
68
69 } // namespace
70
71 LeakDetectorImpl::LeakDetectorImpl(uint64_t mapping_addr,
72 uint64_t mapping_size,
73 int size_suspicion_threshold,
74 int call_stack_suspicion_threshold,
75 bool verbose)
76 : num_stack_tables_(0),
77 address_map_(kAddressMapNumBuckets),
78 size_leak_analyzer_(kRankedListSize, size_suspicion_threshold),
79 size_entries_(kNumSizeEntries, {0}),
80 mapping_addr_(mapping_addr),
81 mapping_size_(mapping_size),
82 call_stack_suspicion_threshold_(call_stack_suspicion_threshold),
83 verbose_(verbose) {
84 }
85
86 LeakDetectorImpl::~LeakDetectorImpl() {
87 for (CallStack* call_stack : call_stacks_)
88 CustomAllocator::Free(call_stack, sizeof(CallStack));
jar (doing other things) 2015/08/21 02:48:43 nit: Although not necessary, it would probably be
Simon Que 2015/08/21 21:59:20 Done. I just cleared the containers.
89
90 // Free any call stack tables.
91 for (AllocSizeEntry& entry : size_entries_) {
92 CallStackTable* table = entry.stack_table;
93 if (!table)
94 continue;
95 table->~CallStackTable();
96 CustomAllocator::Free(table, sizeof(CallStackTable));
97 }
98 }
99
100 bool LeakDetectorImpl::ShouldGetStackTraceForSize(size_t size) const {
101 return size_entries_[SizeToIndex(size)].stack_table != nullptr;
102 }
103
104 void LeakDetectorImpl::RecordAlloc(
105 const void* ptr, size_t size,
106 int stack_depth, const void* const stack[]) {
107 AllocInfo alloc_info;
108 alloc_info.size = size;
109
110 alloc_size_ += alloc_info.size;
111 ++num_allocs_;
112
113 AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
114 ++entry->num_allocs;
115
116 if (stack_depth > 0) {
117 CallStack* call_stack = GetCallStack(stack_depth, stack);
118 alloc_info.call_stack = call_stack;
119
120 ++num_allocs_with_call_stack_;
121
122 if (entry->stack_table)
123 entry->stack_table->Add(call_stack);
124 }
125
126 address_map_.insert(std::pair<const void*, AllocInfo>(ptr, alloc_info));
127 }
128
129 void LeakDetectorImpl::RecordFree(const void* ptr) {
130 // Look up entry.
131 auto iter = address_map_.find(ptr);
132 if (iter == address_map_.end())
133 return;
134
135 const AllocInfo& alloc_info = iter->second;
136
137 AllocSizeEntry* entry = &size_entries_[SizeToIndex(alloc_info.size)];
138 ++entry->num_frees;
139
140 const CallStack* call_stack = alloc_info.call_stack;
141 if (call_stack) {
142 if (entry->stack_table)
143 entry->stack_table->Remove(call_stack);
144 }
145 ++num_frees_;
146 free_size_ += alloc_info.size;
147
148 address_map_.erase(iter);
149 }
150
151 void LeakDetectorImpl::TestForLeaks() {
152 // Add net alloc counts for each size to a ranked list.
153 RankedList size_ranked_list(kRankedListSize);
154 for (size_t i = 0; i < size_entries_.size(); ++i) {
155 const AllocSizeEntry& entry = size_entries_[i];
156 ValueType size_value(IndexToSize(i));
157 size_ranked_list.Add(size_value, entry.num_allocs - entry.num_frees);
158 }
159 size_leak_analyzer_.AddSample(size_ranked_list);
160
161 // Dump out the top entries.
162 char buf[0x4000];
163 if (verbose_) {
164 buf[0] = '\0';
165 size_leak_analyzer_.Dump(buf, sizeof(buf));
jar (doing other things) 2015/08/21 02:48:43 nit: Is it ok for this dump to be empty? Perhaps
Simon Que 2015/08/21 21:59:20 Done.
166 PrintWithPidOnEachLine(buf);
167 }
168
169 // Get suspected leaks by size.
170 for (const ValueType& size_value : size_leak_analyzer_.suspected_leaks()) {
171 uint32_t size = size_value.size();
172 AllocSizeEntry* entry = &size_entries_[SizeToIndex(size)];
173 if (entry->stack_table)
174 continue;
175 snprintf(buf, sizeof(buf), "Adding stack table for size %u\n", size);
176 PrintWithPidOnEachLine(buf);
177 entry->stack_table = new(CustomAllocator::Allocate(sizeof(CallStackTable)))
178 CallStackTable(call_stack_suspicion_threshold_);
179 ++num_stack_tables_;
180 }
181
182 // Check for leaks in each CallStackTable. It makes sense to this before
183 // checking the size allocations, because that could potentially create new
184 // CallStackTable. However, the overhead to check a new CallStackTable is
185 // small since this function is run very rarely. So handle the leak checks of
186 // Tier 2 here.
187 for (size_t i = 0; i < size_entries_.size(); ++i) {
188 const AllocSizeEntry& entry = size_entries_[i];
189 CallStackTable* stack_table = entry.stack_table;
190 if (!stack_table || stack_table->empty())
191 continue;
192
193 if (verbose_) {
194 // Dump table info.
195 snprintf(buf, sizeof(buf), "Stack table for size %zu:\n", IndexToSize(i));
196 PrintWithPidOnEachLine(buf);
197
198 buf[0] = '\0';
199 stack_table->Dump(buf, sizeof(buf));
200 PrintWithPidOnEachLine(buf);
201 }
202
203 // Get suspected leaks by call stack.
204 stack_table->TestForLeaks();
205 const LeakAnalyzer& leak_analyzer = stack_table->leak_analyzer();
206 for (const ValueType& call_stack_value : leak_analyzer.suspected_leaks()) {
207 const CallStack* call_stack = call_stack_value.call_stack();
208 int offset = snprintf(buf, sizeof(buf),
209 "Suspected call stack for size %zu, %p:\n",
210 IndexToSize(i), call_stack);
211 for (size_t j = 0; j < call_stack->depth; ++j) {
212 offset += snprintf(buf + offset, sizeof(buf) - offset,
213 "\t%" PRIxPTR "\n", GetOffset(call_stack->stack[j]));
214 }
215 PrintWithPidOnEachLine(buf);
216 }
217 }
218 }
219
220 void LeakDetectorImpl::DumpStats() const {
221 char buf[1024];
222 snprintf(buf, sizeof(buf),
223 "Alloc size: %" PRIu64"\n"
224 "Free size: %" PRIu64 "\n"
225 "Net alloc size: %" PRIu64 "\n"
226 "Number of stack tables: %u\n"
227 "Percentage of allocs with stack traces: %.2f%%\n"
228 "Number of call stack buckets: %zu\n",
229 alloc_size_, free_size_, alloc_size_ - free_size_, num_stack_tables_,
230 num_allocs_ ? 100.0f * num_allocs_with_call_stack_ / num_allocs_ : 0,
231 call_stacks_.bucket_count());
232 PrintWithPidOnEachLine(buf);
233 }
234
235 size_t LeakDetectorImpl::AddressHash::operator() (const void* ptr) const {
236 return base::Hash(reinterpret_cast<const char*>(&ptr), sizeof(ptr));
jar (doing other things) 2015/08/21 02:48:43 Two things: a) I'm surprised you took the address
Simon Que 2015/08/21 05:44:05 Is there a problem with accessing an argument on t
jar (doing other things) 2015/08/21 17:32:31 You are correct. My comment was mistaken.
Simon Que 2015/08/21 21:59:20 Perhaps it would be more clear to have this operat
Simon Que 2015/08/23 23:30:53 Done.
237 }
238
239 size_t LeakDetectorImpl::CallStackHash::operator() (
240 const CallStack* call_stack) const {
241 // Generate hash from call stack.
242 return base::Hash(reinterpret_cast<const char*>(call_stack->stack),
243 sizeof(*(call_stack->stack)) * call_stack->depth);
jar (doing other things) 2015/08/21 17:32:31 Is there any padding in "*(call_stack->stack)"? I
Simon Que 2015/08/21 21:59:20 |call_stack->stack| is an array of ptrs. And |call
jar (doing other things) 2015/08/22 00:21:47 I should have been more explicit.... I believe th
244 }
245
246 CallStack* LeakDetectorImpl::GetCallStack(
247 int depth, const void* const stack[]) {
248 // Temporarily create a call stack object for lookup in |call_stacks_|.
249 CallStack temp;
250 temp.depth = depth;
251 temp.stack = const_cast<const void**>(stack);
252
253 auto iter = call_stacks_.find(&temp);
254 if (iter != call_stacks_.end())
255 return *iter;
256
257 // Since |call_stacks_| stores CallStack pointers rather than actual objects,
258 // create new call objects manually here.
259 CallStack* new_call_stack =
260 new(CustomAllocator::Allocate(sizeof(CallStack))) CallStack;
261 memset(new_call_stack, 0, sizeof(*new_call_stack));
262 new_call_stack->depth = depth;
263 new_call_stack->hash = call_stacks_.hash_function()(&temp);
264 new_call_stack->stack =
265 reinterpret_cast<const void**>(
266 CustomAllocator::Allocate(sizeof(*stack) * depth));
267 std::copy(stack, stack + depth, new_call_stack->stack);
268
269 call_stacks_.insert(new_call_stack);
270 return new_call_stack;
271 }
272
273 uintptr_t LeakDetectorImpl::GetOffset(const void *ptr) const {
274 uintptr_t ptr_value = reinterpret_cast<uintptr_t>(ptr);
275 if (ptr_value >= mapping_addr_ && ptr_value < mapping_addr_ + mapping_size_)
276 return ptr_value - mapping_addr_;
277 return ptr_value;
278 }
279
280 } // namespace leak_detector
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698