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 <ranked_list.h> |
| 12 |
| 13 // This class looks for possible leak patterns in allocation data over time. |
| 14 // The typename T can be any data type associated with an allocation class, e.g. |
| 15 // size or call stack. |
| 16 |
| 17 namespace leak_detector { |
| 18 |
| 19 template <typename T> |
| 20 class LeakAnalyzer { |
| 21 public: |
| 22 // For local allocations. |
| 23 typedef void* (*Allocator)(size_t size); |
| 24 typedef void (*DeAllocator)(void* ptr); |
| 25 |
| 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 LeakAnalyzer(int ranking_size, int num_suspicions_threshold, |
| 44 Allocator alloc, DeAllocator dealloc, |
| 45 StringPrint* string_print) |
| 46 : ranking_size_(ranking_size), |
| 47 score_increase_(1 << num_suspicions_threshold), |
| 48 score_threshold_((1 << num_suspicions_threshold) * 2 - 1), |
| 49 num_suspected_leaks_(0), |
| 50 alloc_(alloc), |
| 51 dealloc_(dealloc), |
| 52 ranked_entries_(ranking_size, alloc, dealloc), |
| 53 prev_ranked_entries_(ranking_size, alloc, dealloc), |
| 54 string_print_(string_print) { |
| 55 size_t suspected_histogram_size = ranking_size * sizeof(SuspicionEntry); |
| 56 suspected_histogram_ = |
| 57 static_cast<SuspicionEntry*>(alloc_(suspected_histogram_size)); |
| 58 memset(suspected_histogram_, 0, suspected_histogram_size); |
| 59 |
| 60 size_t suspected_leak_size = ranking_size * sizeof(T); |
| 61 suspected_leaks_ = static_cast<T*>(alloc_(suspected_leak_size)); |
| 62 memset(suspected_leaks_, 0, suspected_leak_size); |
| 63 } |
| 64 |
| 65 ~LeakAnalyzer() { |
| 66 dealloc_(suspected_histogram_); |
| 67 dealloc_(suspected_leaks_); |
| 68 |
| 69 suspected_histogram_ = NULL; |
| 70 suspected_leaks_ = NULL; |
| 71 } |
| 72 |
| 73 // Take in a list of allocations, sorted by count. |
| 74 void AddSample(const RankedList<T>& ranked_list) { |
| 75 // Save the ranked entries from the previous call. |
| 76 prev_ranked_entries_ = ranked_entries_; |
| 77 |
| 78 // Save the current entries. |
| 79 ranked_entries_ = ranked_list; |
| 80 |
| 81 RankedList<T> ranked_deltas(ranking_size_, alloc_, dealloc_); |
| 82 for (int i = 0; i < ranked_list.size(); ++i) { |
| 83 const RankedEntry* entry = |
| 84 reinterpret_cast<const RankedEntry*>(&ranked_list.entries()[i]); |
| 85 |
| 86 // Determine what count was recorded for this value last time. |
| 87 const RankedEntry* prev_entry = GetPreviousEntryWithValue(entry->value); |
| 88 if (prev_entry) |
| 89 ranked_deltas.Add(entry->value, entry->count - prev_entry->count); |
| 90 } |
| 91 |
| 92 AnalyzeDeltas(ranked_deltas); |
| 93 } |
| 94 |
| 95 // Log the leak detector's top sizes and suspected sizes. |buffer| is a char |
| 96 // buffer of size |buffer_size| that can eventually be logged. |
| 97 int Dump(char* buffer, const int buffer_size) const { |
| 98 int size_remaining = buffer_size; |
| 99 int attempted_size = 0; |
| 100 |
| 101 // Dump the top entries. |
| 102 if (size_remaining > 0) { |
| 103 attempted_size = |
| 104 snprintf(buffer, size_remaining, "***** Top %d %s *****\n", |
| 105 ranked_entries_.size(), string_print_->ValueTypeName(true)); |
| 106 size_remaining -= attempted_size; |
| 107 buffer += attempted_size; |
| 108 } |
| 109 |
| 110 for (int i = 0; i < ranked_entries_.size() && size_remaining > 0; ++i) { |
| 111 const RankedEntry& entry = ranked_entries_.entry(i); |
| 112 if (entry.count == 0) |
| 113 continue; |
| 114 |
| 115 // Determine what count was recorded for this value last time. |
| 116 const RankedEntry* prev_entry = GetPreviousEntryWithValue(entry.value); |
| 117 |
| 118 char prev_entry_buffer[256]; |
| 119 prev_entry_buffer[0] = '\0'; |
| 120 if (prev_entry) |
| 121 sprintf(prev_entry_buffer, "(%10d)", entry.count - prev_entry->count); |
| 122 |
| 123 int attempted_size = |
| 124 snprintf(buffer, size_remaining, "%s: %10u %s\n", |
| 125 string_print_->ValueToString(entry.value, true), entry.count, |
| 126 prev_entry ? prev_entry_buffer : ""); |
| 127 size_remaining -= attempted_size; |
| 128 buffer += attempted_size; |
| 129 } |
| 130 |
| 131 // Now report the suspected sizes. |
| 132 if (size_remaining > 0) { |
| 133 attempted_size = snprintf(buffer, size_remaining, "Suspected %s: ", |
| 134 string_print_->ValueTypeName(true)); |
| 135 size_remaining -= attempted_size; |
| 136 buffer += attempted_size; |
| 137 } |
| 138 if (num_suspected_leaks_) { |
| 139 for (int i = 0; i < num_suspected_leaks_ && size_remaining > 0; ++i) { |
| 140 attempted_size = |
| 141 snprintf(buffer, size_remaining, "%s, ", |
| 142 string_print_->ValueToString(suspected_leaks_[i], false)); |
| 143 size_remaining -= attempted_size; |
| 144 buffer += attempted_size; |
| 145 } |
| 146 // Erase the last comma and space. |
| 147 buffer -= 2; |
| 148 size_remaining += 2; |
| 149 } |
| 150 if (size_remaining > 0) { |
| 151 attempted_size = snprintf(buffer, size_remaining, "\n"); |
| 152 size_remaining -= attempted_size; |
| 153 buffer += attempted_size; |
| 154 } |
| 155 |
| 156 // Return the number of bytes written. |
| 157 if (size_remaining >= 0) |
| 158 return buffer_size - size_remaining; |
| 159 return buffer_size; |
| 160 } |
| 161 |
| 162 // Used to report suspected leaks. |
| 163 const T* suspected_leaks() const { |
| 164 return suspected_leaks_; |
| 165 } |
| 166 int num_suspected_leaks() const { |
| 167 return num_suspected_leaks_; |
| 168 } |
| 169 |
| 170 private: |
| 171 using RankedEntry = typename RankedList<T>::Entry; |
| 172 |
| 173 // An entry that is suspected as a possible leak. |score| is the level of |
| 174 // suspicion, which can increase over time. Once it reaches |score_threshold_| |
| 175 // the entry is reported as a suspected leak in |suspected_leaks_|. |
| 176 struct SuspicionEntry { |
| 177 int score; |
| 178 T value; |
| 179 |
| 180 // Flag indicating that this entry is valid, used in a fixed-size array. |
| 181 bool valid; |
| 182 }; |
| 183 |
| 184 // Analyze a list of allocation count deltas from the previous iteration. If |
| 185 // anything looks like a possible leak, update the suspicion scores. |
| 186 void AnalyzeDeltas(const RankedList<T>& ranked_deltas) { |
| 187 // First, let the suspicion scores decay to deprecate older suspicions. |
| 188 for (int i = 0; i < ranking_size_; ++i) { |
| 189 suspected_histogram_[i].score /= 2; |
| 190 // Invalidate entries with scores of 0 so that entries that are no longer |
| 191 // under suspicion do not take up a slot that could be occupied by a new |
| 192 // entry that is under suspicion. |
| 193 if (suspected_histogram_[i].score == 0) |
| 194 suspected_histogram_[i].valid = false; |
| 195 } |
| 196 |
| 197 const RankedEntry* entries = |
| 198 reinterpret_cast<const RankedEntry*>(ranked_deltas.entries()); |
| 199 bool found_drop = false; |
| 200 int drop_index = -1; |
| 201 for (int i = 0; i < ranked_deltas.size() - 1; ++i) { |
| 202 const RankedEntry& entry = entries[i]; |
| 203 const RankedEntry& next_entry = entries[i + 1]; |
| 204 |
| 205 // If the first entry is 0, that means all deltas are 0 or negative. Do |
| 206 // not treat this as a suspicion of leaks; just quit. |
| 207 if (i == 0 && entry.count == 0) |
| 208 break; |
| 209 |
| 210 // Find the first major drop in values. |
| 211 if (entry.count > next_entry.count * 2) { |
| 212 found_drop = true; |
| 213 drop_index = i + 1; |
| 214 break; |
| 215 } |
| 216 } |
| 217 |
| 218 // Take the pre-drop sizes and increase their suspicion score. |
| 219 for (int i = 0; found_drop && i < drop_index; ++i) { |
| 220 for (int j = 0; j < ranking_size_; ++j) { |
| 221 SuspicionEntry* suspected = &suspected_histogram_[j]; |
| 222 // Add a new suspected with the current value if there's an empty slot. |
| 223 if (!suspected->valid) { |
| 224 suspected->valid = true; |
| 225 suspected->value = entries[i].value; |
| 226 } |
| 227 if (suspected->value == entries[i].value) { |
| 228 suspected->score += score_increase_; |
| 229 break; |
| 230 } |
| 231 } |
| 232 } |
| 233 |
| 234 // Now check the leak suspicion scores. |
| 235 num_suspected_leaks_ = 0; |
| 236 for (int i = 0; |
| 237 i < ranking_size_ && num_suspected_leaks_ < ranking_size_; |
| 238 ++i) { |
| 239 // Only report suspected values that have accumulated a suspicion score. |
| 240 // This is achieved by maintaining suspicion for several cycles, with few |
| 241 // skips. |
| 242 const SuspicionEntry& suspected = suspected_histogram_[i]; |
| 243 if (!suspected.valid || suspected.score < score_threshold_) { |
| 244 continue; |
| 245 } |
| 246 // Store the size. |
| 247 suspected_leaks_[num_suspected_leaks_++] = suspected.value; |
| 248 } |
| 249 } |
| 250 |
| 251 // Returns the RankedEntry from the previous analysis that had the given |
| 252 // value, or NULL if it didn't exist. |
| 253 const RankedEntry* GetPreviousEntryWithValue(const T& value) const { |
| 254 // Determine what count was recorded for this value last time. |
| 255 for (int i = 0; i < prev_ranked_entries_.size(); ++i) { |
| 256 if (prev_ranked_entries_.entry(i).value == value) |
| 257 return &prev_ranked_entries_.entry(i); |
| 258 } |
| 259 return NULL; |
| 260 } |
| 261 |
| 262 // Look for the top |ranking_size_| entries when analyzing leaks. |
| 263 const int ranking_size_; |
| 264 |
| 265 // Each time an entry is suspected as a possible leak, increase its suspicion |
| 266 // score by this much. |
| 267 const int score_increase_; |
| 268 |
| 269 // Report suspected leaks when the suspicion score reaches this value. |
| 270 const int score_threshold_; |
| 271 |
| 272 // The number of currently reported suspected leaks. |
| 273 int num_suspected_leaks_; |
| 274 |
| 275 // For local allocation. |
| 276 const Allocator alloc_; |
| 277 const DeAllocator dealloc_; |
| 278 |
| 279 // Points to an array to track allocation values and their suspicion scores. |
| 280 SuspicionEntry *suspected_histogram_; |
| 281 |
| 282 // Array of allocated values that passed the suspicion threshold and are being |
| 283 // reported. |
| 284 T* suspected_leaks_; |
| 285 |
| 286 // The most recent allocation entries, since the last call to AddSample(). |
| 287 RankedList<T> ranked_entries_; |
| 288 // The previous allocation entries, from before the last call to AddSample(). |
| 289 RankedList<T> prev_ranked_entries_; |
| 290 |
| 291 // For logging. |
| 292 StringPrint* string_print_; |
| 293 |
| 294 DISALLOW_COPY_AND_ASSIGN(LeakAnalyzer); |
| 295 }; |
| 296 |
| 297 } // namespace leak_detector |
| 298 |
| 299 #endif // _LEAK_ANALYZER_H_ |
OLD | NEW |