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, |
| 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; |
| 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 |