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 #ifndef BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ 6 #define BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ 7 8 #include <stddef.h> 9 #include <stdint.h> 10 11 #include <utility> 12 13 #include "base/bits.h" 14 #include "base/logging.h" 15 #include "base/macros.h" 16 #include "base/process/process_metrics.h" 17 #include "base/template_util.h" 18 #include "base/trace_event/heap_profiler_allocation_context.h" 19 20 namespace base { 21 namespace trace_event { 22 23 class AllocationRegisterTest; 24 25 namespace internal { 26 27 // Allocates a region of virtual address space of |size| rounded up to the 28 // system page size. The memory is zeroed by the system. A guard page is 29 // added after the end. 30 void* AllocateGuardedVirtualMemory(size_t size); 31 32 // Frees a region of virtual address space allocated by a call to 33 // |AllocateVirtualMemory|. 34 void FreeGuardedVirtualMemory(void* address, size_t allocated_size); 35 36 // Hash map that mmaps memory only once in the constructor. Its API is 37 // similar to std::unordered_map, only index (KVIndex) is used to address 38 template <size_t NumBuckets, class Key, class Value, class KeyHasher> 39 class FixedHashMap { 40 // To keep things simple we don't call destructors. 41 static_assert(is_trivially_destructible<Key>::value && 42 is_trivially_destructible<Value>::value, 43 "Key and Value shouldn't have destructors"); 44 public: 45 using KVPair = std::pair<const Key, Value>; 46 47 // For implementation simplicity API uses integer index instead 48 // of iterators. Most operations (except FindValidIndex) on KVIndex 49 // are O(1). 50 using KVIndex = size_t; 51 static const KVIndex kInvalidKVIndex = static_cast<KVIndex>(-1); 52 53 // Capacity controls how many items this hash map can hold, and largely 54 // affects memory footprint. FixedHashMap(size_t capacity)55 FixedHashMap(size_t capacity) 56 : num_cells_(capacity), 57 cells_(static_cast<Cell*>( 58 AllocateGuardedVirtualMemory(num_cells_ * sizeof(Cell)))), 59 buckets_(static_cast<Bucket*>( 60 AllocateGuardedVirtualMemory(NumBuckets * sizeof(Bucket)))), 61 free_list_(nullptr), 62 next_unused_cell_(0) {} 63 ~FixedHashMap()64 ~FixedHashMap() { 65 FreeGuardedVirtualMemory(cells_, num_cells_ * sizeof(Cell)); 66 FreeGuardedVirtualMemory(buckets_, NumBuckets * sizeof(Bucket)); 67 } 68 Insert(const Key & key,const Value & value)69 std::pair<KVIndex, bool> Insert(const Key& key, const Value& value) { 70 Cell** p_cell = Lookup(key); 71 Cell* cell = *p_cell; 72 if (cell) { 73 return {static_cast<KVIndex>(cell - cells_), false}; // not inserted 74 } 75 76 // Get a free cell and link it. 77 *p_cell = cell = GetFreeCell(); 78 cell->p_prev = p_cell; 79 cell->next = nullptr; 80 81 // Initialize key/value pair. Since key is 'const Key' this is the 82 // only way to initialize it. 83 new (&cell->kv) KVPair(key, value); 84 85 return {static_cast<KVIndex>(cell - cells_), true}; // inserted 86 } 87 Remove(KVIndex index)88 void Remove(KVIndex index) { 89 DCHECK_LT(index, next_unused_cell_); 90 91 Cell* cell = &cells_[index]; 92 93 // Unlink the cell. 94 *cell->p_prev = cell->next; 95 if (cell->next) { 96 cell->next->p_prev = cell->p_prev; 97 } 98 cell->p_prev = nullptr; // mark as free 99 100 // Add it to the free list. 101 cell->next = free_list_; 102 free_list_ = cell; 103 } 104 Find(const Key & key)105 KVIndex Find(const Key& key) const { 106 Cell* cell = *Lookup(key); 107 return cell ? static_cast<KVIndex>(cell - cells_) : kInvalidKVIndex; 108 } 109 Get(KVIndex index)110 KVPair& Get(KVIndex index) { 111 return cells_[index].kv; 112 } 113 Get(KVIndex index)114 const KVPair& Get(KVIndex index) const { 115 return cells_[index].kv; 116 } 117 118 // Finds next index that has a KVPair associated with it. Search starts 119 // with the specified index. Returns kInvalidKVIndex if nothing was found. 120 // To find the first valid index, call this function with 0. Continue 121 // calling with the last_index + 1 until kInvalidKVIndex is returned. Next(KVIndex index)122 KVIndex Next(KVIndex index) const { 123 for (;index < next_unused_cell_; ++index) { 124 if (cells_[index].p_prev) { 125 return index; 126 } 127 } 128 return kInvalidKVIndex; 129 } 130 131 // Estimates number of bytes used in allocated memory regions. EstimateUsedMemory()132 size_t EstimateUsedMemory() const { 133 size_t page_size = base::GetPageSize(); 134 // |next_unused_cell_| is the first cell that wasn't touched, i.e. 135 // it's the number of touched cells. 136 return bits::Align(sizeof(Cell) * next_unused_cell_, page_size) + 137 bits::Align(sizeof(Bucket) * NumBuckets, page_size); 138 } 139 140 private: 141 friend base::trace_event::AllocationRegisterTest; 142 143 struct Cell { 144 KVPair kv; 145 Cell* next; 146 147 // Conceptually this is |prev| in a doubly linked list. However, buckets 148 // also participate in the bucket's cell list - they point to the list's 149 // head and also need to be linked / unlinked properly. To treat these two 150 // cases uniformly, instead of |prev| we're storing "pointer to a Cell* 151 // that points to this Cell" kind of thing. So |p_prev| points to a bucket 152 // for the first cell in a list, and points to |next| of the previous cell 153 // for any other cell. With that Lookup() is the only function that handles 154 // buckets / cells differently. 155 // If |p_prev| is nullptr, the cell is in the free list. 156 Cell** p_prev; 157 }; 158 159 using Bucket = Cell*; 160 161 // Returns a pointer to the cell that contains or should contain the entry 162 // for |key|. The pointer may point at an element of |buckets_| or at the 163 // |next| member of an element of |cells_|. Lookup(const Key & key)164 Cell** Lookup(const Key& key) const { 165 // The list head is in |buckets_| at the hash offset. 166 Cell** p_cell = &buckets_[Hash(key)]; 167 168 // Chase down the list until the cell that holds |key| is found, 169 // or until the list ends. 170 while (*p_cell && (*p_cell)->kv.first != key) { 171 p_cell = &(*p_cell)->next; 172 } 173 174 return p_cell; 175 } 176 177 // Returns a cell that is not being used to store an entry (either by 178 // recycling from the free list or by taking a fresh cell). GetFreeCell()179 Cell* GetFreeCell() { 180 // First try to re-use a cell from the free list. 181 if (free_list_) { 182 Cell* cell = free_list_; 183 free_list_ = cell->next; 184 return cell; 185 } 186 187 // Otherwise pick the next cell that has not been touched before. 188 size_t idx = next_unused_cell_; 189 next_unused_cell_++; 190 191 // If the hash table has too little capacity (when too little address space 192 // was reserved for |cells_|), |next_unused_cell_| can be an index outside 193 // of the allocated storage. A guard page is allocated there to crash the 194 // program in that case. There are alternative solutions: 195 // - Deal with it, increase capacity by reallocating |cells_|. 196 // - Refuse to insert and let the caller deal with it. 197 // Because free cells are re-used before accessing fresh cells with a higher 198 // index, and because reserving address space without touching it is cheap, 199 // the simplest solution is to just allocate a humongous chunk of address 200 // space. 201 202 DCHECK_LT(next_unused_cell_, num_cells_ + 1); 203 204 return &cells_[idx]; 205 } 206 207 // Returns a value in the range [0, NumBuckets - 1] (inclusive). Hash(const Key & key)208 size_t Hash(const Key& key) const { 209 if (NumBuckets == (NumBuckets & ~(NumBuckets - 1))) { 210 // NumBuckets is a power of 2. 211 return KeyHasher()(key) & (NumBuckets - 1); 212 } else { 213 return KeyHasher()(key) % NumBuckets; 214 } 215 } 216 217 // Number of cells. 218 size_t const num_cells_; 219 220 // The array of cells. This array is backed by mmapped memory. Lower indices 221 // are accessed first, higher indices are accessed only when the |free_list_| 222 // is empty. This is to minimize the amount of resident memory used. 223 Cell* const cells_; 224 225 // The array of buckets (pointers into |cells_|). |buckets_[Hash(key)]| will 226 // contain the pointer to the linked list of cells for |Hash(key)|. 227 // This array is backed by mmapped memory. 228 mutable Bucket* buckets_; 229 230 // The head of the free list. 231 Cell* free_list_; 232 233 // The index of the first element of |cells_| that has not been used before. 234 // If the free list is empty and a new cell is needed, the cell at this index 235 // is used. This is the high water mark for the number of entries stored. 236 size_t next_unused_cell_; 237 238 DISALLOW_COPY_AND_ASSIGN(FixedHashMap); 239 }; 240 241 } // namespace internal 242 243 class TraceEventMemoryOverhead; 244 245 // The allocation register keeps track of all allocations that have not been 246 // freed. Internally it has two hashtables: one for Backtraces and one for 247 // actual allocations. Sizes of both hashtables are fixed, and this class 248 // allocates (mmaps) only in its constructor. 249 class BASE_EXPORT AllocationRegister { 250 public: 251 // Details about an allocation. 252 struct Allocation { 253 const void* address; 254 size_t size; 255 AllocationContext context; 256 }; 257 258 // An iterator that iterates entries in no particular order. 259 class BASE_EXPORT ConstIterator { 260 public: 261 void operator++(); 262 bool operator!=(const ConstIterator& other) const; 263 Allocation operator*() const; 264 265 private: 266 friend class AllocationRegister; 267 using AllocationIndex = size_t; 268 269 ConstIterator(const AllocationRegister& alloc_register, 270 AllocationIndex index); 271 272 const AllocationRegister& register_; 273 AllocationIndex index_; 274 }; 275 276 AllocationRegister(); 277 AllocationRegister(size_t allocation_capacity, size_t backtrace_capacity); 278 279 ~AllocationRegister(); 280 281 // Inserts allocation details into the table. If the address was present 282 // already, its details are updated. |address| must not be null. 283 void Insert(const void* address, 284 size_t size, 285 const AllocationContext& context); 286 287 // Removes the address from the table if it is present. It is ok to call this 288 // with a null pointer. 289 void Remove(const void* address); 290 291 // Finds allocation for the address and fills |out_allocation|. 292 bool Get(const void* address, Allocation* out_allocation) const; 293 294 ConstIterator begin() const; 295 ConstIterator end() const; 296 297 // Estimates memory overhead including |sizeof(AllocationRegister)|. 298 void EstimateTraceMemoryOverhead(TraceEventMemoryOverhead* overhead) const; 299 300 private: 301 friend AllocationRegisterTest; 302 303 // Expect max 1.5M allocations. Number of buckets is 2^18 for optimal 304 // hashing and should be changed together with AddressHasher. 305 static const size_t kAllocationBuckets = 1 << 18; 306 static const size_t kAllocationCapacity = 1500000; 307 308 // Expect max 2^15 unique backtraces. Can be changed to 2^16 without 309 // needing to tweak BacktraceHasher implementation. 310 static const size_t kBacktraceBuckets = 1 << 15; 311 static const size_t kBacktraceCapacity = kBacktraceBuckets; 312 313 struct BacktraceHasher { 314 size_t operator () (const Backtrace& backtrace) const; 315 }; 316 317 using BacktraceMap = internal::FixedHashMap< 318 kBacktraceBuckets, 319 Backtrace, 320 size_t, // Number of references to the backtrace (the key). Incremented 321 // when an allocation that references the backtrace is inserted, 322 // and decremented when the allocation is removed. When the 323 // number drops to zero, the backtrace is removed from the map. 324 BacktraceHasher>; 325 326 struct AllocationInfo { 327 size_t size; 328 const char* type_name; 329 BacktraceMap::KVIndex backtrace_index; 330 }; 331 332 struct AddressHasher { 333 size_t operator () (const void* address) const; 334 }; 335 336 using AllocationMap = internal::FixedHashMap< 337 kAllocationBuckets, 338 const void*, 339 AllocationInfo, 340 AddressHasher>; 341 342 BacktraceMap::KVIndex InsertBacktrace(const Backtrace& backtrace); 343 void RemoveBacktrace(BacktraceMap::KVIndex index); 344 345 Allocation GetAllocation(AllocationMap::KVIndex) const; 346 347 AllocationMap allocations_; 348 BacktraceMap backtraces_; 349 350 DISALLOW_COPY_AND_ASSIGN(AllocationRegister); 351 }; 352 353 } // namespace trace_event 354 } // namespace base 355 356 #endif // BASE_TRACE_EVENT_HEAP_PROFILER_ALLOCATION_REGISTER_H_ 357