Home | History | Annotate | Download | only in trace_event
      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.
     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 
     64   ~FixedHashMap() {
     65     FreeGuardedVirtualMemory(cells_, num_cells_ * sizeof(Cell));
     66     FreeGuardedVirtualMemory(buckets_, NumBuckets * sizeof(Bucket));
     67   }
     68 
     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 
     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 
    105   KVIndex Find(const Key& key) const {
    106     Cell* cell = *Lookup(key);
    107     return cell ? static_cast<KVIndex>(cell - cells_) : kInvalidKVIndex;
    108   }
    109 
    110   KVPair& Get(KVIndex index) {
    111     return cells_[index].kv;
    112   }
    113 
    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.
    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.
    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_|.
    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).
    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).
    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