OLD | NEW |
---|---|
(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 | |
OLD | NEW |