OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 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 // --- | |
6 // Author: Simon Que | |
7 | |
8 #ifndef _LEAK_ANALYZER_H_ | |
9 #define _LEAK_ANALYZER_H_ | |
10 | |
11 #include <map> | |
12 #include <vector> | |
13 | |
14 #include "base/custom_allocator.h" | |
15 #include "ranked_list.h" | |
16 | |
17 // This class looks for possible leak patterns in allocation data over time. | |
18 // The typename T can be any data type associated with an allocation class, e.g. | |
19 // size or call stack. | |
20 | |
21 namespace leak_detector { | |
22 | |
23 template <typename T> | |
24 class LeakAnalyzer { | |
25 public: | |
26 // Interface for printing strings for the template class type. | |
27 class StringPrint { | |
28 public: | |
29 // Gets the string representation of a value. Returns the string stored in | |
30 // |buffer_|. The contents of |buffer_| will change if this is called again | |
31 // with different inputs. | |
32 virtual const char* ValueToString(const T& value, bool spacing_on) = 0; | |
33 | |
34 // Gets the word that describes the value type. | |
35 virtual const char* ValueTypeName(bool is_plural) = 0; | |
36 | |
37 protected: | |
38 // Buffer for returning the string representation of a value. | |
39 static const int kStringPrintBufferSize = 1024; | |
40 char buffer_[kStringPrintBufferSize]; | |
41 }; | |
42 | |
43 template <typename Type> | |
44 using Allocator = STL_Allocator<Type, CustomAllocator>; | |
45 | |
46 LeakAnalyzer(int ranking_size, int num_suspicions_threshold, | |
jar (doing other things)
2015/07/20 22:19:03
Is this a fork of external code? I not, I think t
| |
47 StringPrint* string_print) | |
48 : ranking_size_(ranking_size), | |
49 score_increase_(1 << num_suspicions_threshold), | |
50 score_threshold_((1 << num_suspicions_threshold) * 2 - 1), | |
51 ranked_entries_(ranking_size), | |
52 prev_ranked_entries_(ranking_size), | |
53 string_print_(string_print) { | |
54 suspected_leaks_.reserve(ranking_size); | |
55 } | |
56 | |
57 ~LeakAnalyzer() {} | |
58 | |
59 // Take in a list of allocations, sorted by count. | |
60 void AddSample(const RankedList<T>& ranked_list) { | |
61 // Save the ranked entries from the previous call. | |
62 prev_ranked_entries_ = ranked_entries_; | |
63 | |
64 // Save the current entries. | |
65 ranked_entries_ = ranked_list; | |
66 | |
67 RankedList<T> ranked_deltas(ranking_size_); | |
68 for (int i = 0; i < ranked_list.size(); ++i) { | |
69 const RankedEntry& entry = ranked_list.entry(i); | |
70 | |
71 // Determine what count was recorded for this value last time. | |
72 const RankedEntry* prev_entry = GetPreviousEntryWithValue(entry.value); | |
73 if (prev_entry) | |
74 ranked_deltas.Add(entry.value, entry.count - prev_entry->count); | |
75 } | |
76 | |
77 AnalyzeDeltas(ranked_deltas); | |
78 } | |
79 | |
80 // Log the leak detector's top sizes and suspected sizes. |buffer| is a char | |
81 // buffer of size |buffer_size| that can eventually be logged. | |
82 int Dump(char* buffer, const int buffer_size) const { | |
83 int size_remaining = buffer_size; | |
jar (doing other things)
2015/07/20 22:19:03
why is this giant routine placed in a header, and
| |
84 int attempted_size = 0; | |
85 | |
86 // Dump the top entries. | |
87 if (size_remaining > 0) { | |
88 attempted_size = | |
89 snprintf(buffer, size_remaining, "***** Top %d %s *****\n", | |
90 ranked_entries_.size(), string_print_->ValueTypeName(true)); | |
91 size_remaining -= attempted_size; | |
92 buffer += attempted_size; | |
93 } | |
94 | |
95 for (int i = 0; i < ranked_entries_.size() && size_remaining > 0; ++i) { | |
96 const RankedEntry& entry = ranked_entries_.entry(i); | |
97 if (entry.count == 0) | |
98 continue; | |
99 | |
100 // Determine what count was recorded for this value last time. | |
101 const RankedEntry* prev_entry = GetPreviousEntryWithValue(entry.value); | |
102 | |
103 char prev_entry_buffer[256]; | |
104 prev_entry_buffer[0] = '\0'; | |
105 if (prev_entry) | |
106 sprintf(prev_entry_buffer, "(%10d)", entry.count - prev_entry->count); | |
107 | |
108 int attempted_size = | |
109 snprintf(buffer, size_remaining, "%s: %10u %s\n", | |
110 string_print_->ValueToString(entry.value, true), entry.count, | |
111 prev_entry ? prev_entry_buffer : ""); | |
112 size_remaining -= attempted_size; | |
113 buffer += attempted_size; | |
114 } | |
115 | |
116 // Now report the suspected sizes. | |
117 if (size_remaining > 0) { | |
118 attempted_size = snprintf(buffer, size_remaining, "Suspected %s: ", | |
119 string_print_->ValueTypeName(true)); | |
120 size_remaining -= attempted_size; | |
121 buffer += attempted_size; | |
122 } | |
123 if (size_remaining > 0) { | |
124 for (const T& leak_value : suspected_leaks_) { | |
125 attempted_size = | |
126 snprintf(buffer, size_remaining, "%s, ", | |
127 string_print_->ValueToString(leak_value, false)); | |
128 size_remaining -= attempted_size; | |
129 buffer += attempted_size; | |
130 } | |
131 // Erase the last comma and space. | |
132 buffer -= 2; | |
133 size_remaining += 2; | |
134 } | |
135 if (size_remaining > 0) { | |
136 attempted_size = snprintf(buffer, size_remaining, "\n"); | |
137 size_remaining -= attempted_size; | |
138 buffer += attempted_size; | |
139 } | |
140 | |
141 // Return the number of bytes written. | |
142 if (size_remaining >= 0) | |
143 return buffer_size - size_remaining; | |
144 return buffer_size; | |
145 } | |
146 | |
147 // Used to report suspected leaks. | |
148 const std::vector<T, Allocator<T>>& suspected_leaks() const { | |
149 return suspected_leaks_; | |
150 } | |
151 | |
152 private: | |
153 using RankedEntry = typename RankedList<T>::Entry; | |
154 | |
155 // Analyze a list of allocation count deltas from the previous iteration. If | |
156 // anything looks like a possible leak, update the suspicion scores. | |
157 void AnalyzeDeltas(const RankedList<T>& ranked_deltas) { | |
158 // First, let the suspicion scores decay to deprecate older suspicions. | |
159 auto iter = suspected_histogram_.begin(); | |
160 while (iter != suspected_histogram_.end()) { | |
161 // Suspicion score is the map value. | |
162 iter->second /= 2; | |
163 | |
164 // Erase entries whose suspicion score reaches 0. | |
165 if (iter->second == 0) { | |
166 auto erase_iter = iter++; | |
167 suspected_histogram_.erase(erase_iter); | |
168 } else { | |
169 ++iter; | |
170 } | |
171 } | |
172 | |
173 bool found_drop = false; | |
174 int drop_index = -1; | |
175 for (int i = 0; i < static_cast<int>(ranked_deltas.size()) - 1; ++i) { | |
176 const RankedEntry& entry = ranked_deltas.entry(i); | |
177 const RankedEntry& next_entry = ranked_deltas.entry(i + 1); | |
178 | |
179 // If the first entry is 0, that means all deltas are 0 or negative. Do | |
180 // not treat this as a suspicion of leaks; just quit. | |
181 if (i == 0 && entry.count == 0) | |
182 break; | |
183 | |
184 // Find the first major drop in values. | |
185 if (entry.count > next_entry.count * 2) { | |
186 found_drop = true; | |
187 drop_index = i + 1; | |
188 break; | |
189 } | |
190 } | |
191 | |
192 // Take the pre-drop sizes and increase their suspicion score. | |
193 for (int i = 0; found_drop && i < drop_index; ++i) { | |
194 const T& value = ranked_deltas.entry(i).value; | |
195 | |
196 auto iter = suspected_histogram_.find(value); | |
197 if (iter != suspected_histogram_.end()) { | |
198 iter->second += score_increase_; | |
199 } else if (suspected_histogram_.size() < ranking_size_) { | |
200 // Create a new entry. | |
201 suspected_histogram_[value] = score_increase_; | |
202 } | |
203 } | |
204 | |
205 // Now check the leak suspicion scores. Make sure to erase the suspected | |
206 // leaks from the previous call. | |
207 suspected_leaks_.clear(); | |
208 for (const auto& entry : suspected_histogram_) { | |
209 if (suspected_leaks_.size() > ranking_size_) | |
210 break; | |
211 | |
212 // Only report suspected values that have accumulated a suspicion score. | |
213 // This is achieved by maintaining suspicion for several cycles, with few | |
214 // skips. | |
215 if (entry.second >= score_threshold_) | |
216 suspected_leaks_.emplace_back(entry.first); | |
217 } | |
218 } | |
219 | |
220 // Returns the RankedEntry from the previous analysis that had the given | |
221 // value, or NULL if it didn't exist. | |
222 const RankedEntry* GetPreviousEntryWithValue(const T& value) const { | |
223 // Determine what count was recorded for this value last time. | |
224 for (int i = 0; i < prev_ranked_entries_.size(); ++i) { | |
225 if (prev_ranked_entries_.entry(i).value == value) | |
226 return &prev_ranked_entries_.entry(i); | |
227 } | |
228 return NULL; | |
229 } | |
230 | |
231 // Look for the top |ranking_size_| entries when analyzing leaks. | |
232 const int ranking_size_; | |
233 | |
234 // Each time an entry is suspected as a possible leak, increase its suspicion | |
235 // score by this much. | |
236 const int score_increase_; | |
237 | |
238 // Report suspected leaks when the suspicion score reaches this value. | |
239 const int score_threshold_; | |
240 | |
241 // A mapping of allocation values to suspicion score. All allocations in this | |
242 // container are suspected leaks. The score can increase or decrease over | |
243 // time. Once the score reaches |score_threshold_|, the entry is reported as | |
244 // a suspected leak in |suspected_leaks_|. | |
245 std::map<T, uint32, std::less<T>, Allocator<std::pair<T, uint32>>> | |
246 suspected_histogram_; | |
247 | |
248 // Array of allocated values that passed the suspicion threshold and are being | |
249 // reported. | |
250 std::vector<T, Allocator<T>> suspected_leaks_; | |
251 | |
252 // The most recent allocation entries, since the last call to AddSample(). | |
253 RankedList<T> ranked_entries_; | |
254 // The previous allocation entries, from before the last call to AddSample(). | |
255 RankedList<T> prev_ranked_entries_; | |
256 | |
257 // For logging. | |
258 StringPrint* string_print_; | |
259 | |
260 DISALLOW_COPY_AND_ASSIGN(LeakAnalyzer); | |
261 }; | |
262 | |
263 } // namespace leak_detector | |
264 | |
265 #endif // _LEAK_ANALYZER_H_ | |
OLD | NEW |