Home | History | Annotate | Download | only in trace_event
      1 // Copyright 2016 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_MEMORY_USAGE_ESTIMATOR_H_
      6 #define BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
      7 
      8 #include <stdint.h>
      9 
     10 #include <array>
     11 #include <deque>
     12 #include <list>
     13 #include <map>
     14 #include <memory>
     15 #include <queue>
     16 #include <set>
     17 #include <stack>
     18 #include <string>
     19 #include <type_traits>
     20 #include <unordered_map>
     21 #include <unordered_set>
     22 #include <vector>
     23 
     24 #include "base/base_export.h"
     25 #include "base/containers/linked_list.h"
     26 #include "base/strings/string16.h"
     27 #include "base/template_util.h"
     28 
     29 // Composable memory usage estimators.
     30 //
     31 // This file defines set of EstimateMemoryUsage(object) functions that return
     32 // approximate memory usage of their argument.
     33 //
     34 // The ultimate goal is to make memory usage estimation for a class simply a
     35 // matter of aggregating EstimateMemoryUsage() results over all fields.
     36 //
     37 // That is achieved via composability: if EstimateMemoryUsage() is defined
     38 // for T then EstimateMemoryUsage() is also defined for any combination of
     39 // containers holding T (e.g. std::map<int, std::vector<T>>).
     40 //
     41 // There are two ways of defining EstimateMemoryUsage() for a type:
     42 //
     43 // 1. As a global function 'size_t EstimateMemoryUsage(T)' in
     44 //    in base::trace_event namespace.
     45 //
     46 // 2. As 'size_t T::EstimateMemoryUsage() const' method. In this case
     47 //    EstimateMemoryUsage(T) function in base::trace_event namespace is
     48 //    provided automatically.
     49 //
     50 // Here is an example implementation:
     51 //
     52 // size_t foo::bar::MyClass::EstimateMemoryUsage() const {
     53 //   return base::trace_event::EstimateMemoryUsage(name_) +
     54 //          base::trace_event::EstimateMemoryUsage(id_) +
     55 //          base::trace_event::EstimateMemoryUsage(items_);
     56 // }
     57 //
     58 // The approach is simple: first call EstimateMemoryUsage() on all members,
     59 // then recursively fix compilation errors that are caused by types not
     60 // implementing EstimateMemoryUsage().
     61 
     62 namespace base {
     63 namespace trace_event {
     64 
     65 // Declarations
     66 
     67 // If T declares 'EstimateMemoryUsage() const' member function, then
     68 // global function EstimateMemoryUsage(T) is available, and just calls
     69 // the member function.
     70 template <class T>
     71 auto EstimateMemoryUsage(const T& object)
     72     -> decltype(object.EstimateMemoryUsage());
     73 
     74 // String
     75 
     76 template <class C, class T, class A>
     77 size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string);
     78 
     79 // Arrays
     80 
     81 template <class T, size_t N>
     82 size_t EstimateMemoryUsage(const std::array<T, N>& array);
     83 
     84 template <class T, size_t N>
     85 size_t EstimateMemoryUsage(T (&array)[N]);
     86 
     87 template <class T>
     88 size_t EstimateMemoryUsage(const T* array, size_t array_length);
     89 
     90 // std::unique_ptr
     91 
     92 template <class T, class D>
     93 size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr);
     94 
     95 template <class T, class D>
     96 size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array,
     97                            size_t array_length);
     98 
     99 // std::shared_ptr
    100 
    101 template <class T>
    102 size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr);
    103 
    104 // Containers
    105 
    106 template <class F, class S>
    107 size_t EstimateMemoryUsage(const std::pair<F, S>& pair);
    108 
    109 template <class T, class A>
    110 size_t EstimateMemoryUsage(const std::vector<T, A>& vector);
    111 
    112 template <class T, class A>
    113 size_t EstimateMemoryUsage(const std::list<T, A>& list);
    114 
    115 template <class T>
    116 size_t EstimateMemoryUsage(const base::LinkedList<T>& list);
    117 
    118 template <class T, class C, class A>
    119 size_t EstimateMemoryUsage(const std::set<T, C, A>& set);
    120 
    121 template <class T, class C, class A>
    122 size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set);
    123 
    124 template <class K, class V, class C, class A>
    125 size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map);
    126 
    127 template <class K, class V, class C, class A>
    128 size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map);
    129 
    130 template <class T, class H, class KE, class A>
    131 size_t EstimateMemoryUsage(const std::unordered_set<T, H, KE, A>& set);
    132 
    133 template <class T, class H, class KE, class A>
    134 size_t EstimateMemoryUsage(const std::unordered_multiset<T, H, KE, A>& set);
    135 
    136 template <class K, class V, class H, class KE, class A>
    137 size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map);
    138 
    139 template <class K, class V, class H, class KE, class A>
    140 size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map);
    141 
    142 template <class T, class A>
    143 size_t EstimateMemoryUsage(const std::deque<T, A>& deque);
    144 
    145 template <class T, class C>
    146 size_t EstimateMemoryUsage(const std::queue<T, C>& queue);
    147 
    148 template <class T, class C>
    149 size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue);
    150 
    151 template <class T, class C>
    152 size_t EstimateMemoryUsage(const std::stack<T, C>& stack);
    153 
    154 // TODO(dskiba):
    155 //   std::forward_list
    156 
    157 // Definitions
    158 
    159 namespace internal {
    160 
    161 // HasEMU<T>::value is true iff EstimateMemoryUsage(T) is available.
    162 // (This is the default version, which is false.)
    163 template <class T, class X = void>
    164 struct HasEMU : std::false_type {};
    165 
    166 // This HasEMU specialization is only picked up if there exists function
    167 // EstimateMemoryUsage(const T&) that returns size_t. Simpler ways to
    168 // achieve this don't work on MSVC.
    169 template <class T>
    170 struct HasEMU<
    171     T,
    172     typename std::enable_if<std::is_same<
    173         size_t,
    174         decltype(EstimateMemoryUsage(std::declval<const T&>()))>::value>::type>
    175     : std::true_type {};
    176 
    177 // EMUCaller<T> does three things:
    178 // 1. Defines Call() method that calls EstimateMemoryUsage(T) if it's
    179 //    available.
    180 // 2. If EstimateMemoryUsage(T) is not available, but T has trivial dtor
    181 //    (i.e. it's POD, integer, pointer, enum, etc.) then it defines Call()
    182 //    method that returns 0. This is useful for containers, which allocate
    183 //    memory regardless of T (also for cases like std::map<int, MyClass>).
    184 // 3. Finally, if EstimateMemoryUsage(T) is not available, then it triggers
    185 //    a static_assert with a helpful message. That cuts numbers of errors
    186 //    considerably - if you just call EstimateMemoryUsage(T) but it's not
    187 //    available for T, then compiler will helpfully list *all* possible
    188 //    variants of it, with an explanation for each.
    189 template <class T, class X = void>
    190 struct EMUCaller {
    191   // std::is_same<> below makes static_assert depend on T, in order to
    192   // prevent it from asserting regardless instantiation.
    193   static_assert(std::is_same<T, std::false_type>::value,
    194                 "Neither global function 'size_t EstimateMemoryUsage(T)' "
    195                 "nor member function 'size_t T::EstimateMemoryUsage() const' "
    196                 "is defined for the type.");
    197 
    198   static size_t Call(const T&) { return 0; }
    199 };
    200 
    201 template <class T>
    202 struct EMUCaller<T, typename std::enable_if<HasEMU<T>::value>::type> {
    203   static size_t Call(const T& value) { return EstimateMemoryUsage(value); }
    204 };
    205 
    206 template <class T>
    207 struct EMUCaller<
    208     T,
    209     typename std::enable_if<!HasEMU<T>::value &&
    210                             is_trivially_destructible<T>::value>::type> {
    211   static size_t Call(const T& value) { return 0; }
    212 };
    213 
    214 // Returns reference to the underlying container of a container adapter.
    215 // Works for std::stack, std::queue and std::priority_queue.
    216 template <class A>
    217 const typename A::container_type& GetUnderlyingContainer(const A& adapter) {
    218   struct ExposedAdapter : A {
    219     using A::c;
    220   };
    221   return adapter.*&ExposedAdapter::c;
    222 }
    223 
    224 }  // namespace internal
    225 
    226 // Proxy that deducts T and calls EMUCaller<T>.
    227 // To be used by EstimateMemoryUsage() implementations for containers.
    228 template <class T>
    229 size_t EstimateItemMemoryUsage(const T& value) {
    230   return internal::EMUCaller<T>::Call(value);
    231 }
    232 
    233 template <class I>
    234 size_t EstimateIterableMemoryUsage(const I& iterable) {
    235   size_t memory_usage = 0;
    236   for (const auto& item : iterable) {
    237     memory_usage += EstimateItemMemoryUsage(item);
    238   }
    239   return memory_usage;
    240 }
    241 
    242 // Global EstimateMemoryUsage(T) that just calls T::EstimateMemoryUsage().
    243 template <class T>
    244 auto EstimateMemoryUsage(const T& object)
    245     -> decltype(object.EstimateMemoryUsage()) {
    246   static_assert(
    247       std::is_same<decltype(object.EstimateMemoryUsage()), size_t>::value,
    248       "'T::EstimateMemoryUsage() const' must return size_t.");
    249   return object.EstimateMemoryUsage();
    250 }
    251 
    252 // String
    253 
    254 template <class C, class T, class A>
    255 size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string) {
    256   using string_type = std::basic_string<C, T, A>;
    257   using value_type = typename string_type::value_type;
    258   // C++11 doesn't leave much room for implementors - std::string can
    259   // use short string optimization, but that's about it. We detect SSO
    260   // by checking that c_str() points inside |string|.
    261   const uint8_t* cstr = reinterpret_cast<const uint8_t*>(string.c_str());
    262   const uint8_t* inline_cstr = reinterpret_cast<const uint8_t*>(&string);
    263   if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) {
    264     // SSO string
    265     return 0;
    266   }
    267   return (string.capacity() + 1) * sizeof(value_type);
    268 }
    269 
    270 // Use explicit instantiations from the .cc file (reduces bloat).
    271 extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::string&);
    272 extern template BASE_EXPORT size_t EstimateMemoryUsage(const string16&);
    273 
    274 // Arrays
    275 
    276 template <class T, size_t N>
    277 size_t EstimateMemoryUsage(const std::array<T, N>& array) {
    278   return EstimateIterableMemoryUsage(array);
    279 }
    280 
    281 template <class T, size_t N>
    282 size_t EstimateMemoryUsage(T (&array)[N]) {
    283   return EstimateIterableMemoryUsage(array);
    284 }
    285 
    286 template <class T>
    287 size_t EstimateMemoryUsage(const T* array, size_t array_length) {
    288   size_t memory_usage = sizeof(T) * array_length;
    289   for (size_t i = 0; i != array_length; ++i) {
    290     memory_usage += EstimateItemMemoryUsage(array[i]);
    291   }
    292   return memory_usage;
    293 }
    294 
    295 // std::unique_ptr
    296 
    297 template <class T, class D>
    298 size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr) {
    299   return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0;
    300 }
    301 
    302 template <class T, class D>
    303 size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array,
    304                            size_t array_length) {
    305   return EstimateMemoryUsage(array.get(), array_length);
    306 }
    307 
    308 // std::shared_ptr
    309 
    310 template <class T>
    311 size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr) {
    312   auto use_count = ptr.use_count();
    313   if (use_count == 0) {
    314     return 0;
    315   }
    316   // Model shared_ptr after libc++,
    317   // see __shared_ptr_pointer from include/memory
    318   struct SharedPointer {
    319     void* vtbl;
    320     long shared_owners;
    321     long shared_weak_owners;
    322     T* value;
    323   };
    324   // If object of size S shared N > S times we prefer to (potentially)
    325   // overestimate than to return 0.
    326   return sizeof(SharedPointer) +
    327          (EstimateItemMemoryUsage(*ptr) + (use_count - 1)) / use_count;
    328 }
    329 
    330 // std::pair
    331 
    332 template <class F, class S>
    333 size_t EstimateMemoryUsage(const std::pair<F, S>& pair) {
    334   return EstimateItemMemoryUsage(pair.first) +
    335          EstimateItemMemoryUsage(pair.second);
    336 }
    337 
    338 // std::vector
    339 
    340 template <class T, class A>
    341 size_t EstimateMemoryUsage(const std::vector<T, A>& vector) {
    342   return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector);
    343 }
    344 
    345 // std::list
    346 
    347 template <class T, class A>
    348 size_t EstimateMemoryUsage(const std::list<T, A>& list) {
    349   using value_type = typename std::list<T, A>::value_type;
    350   struct Node {
    351     Node* prev;
    352     Node* next;
    353     value_type value;
    354   };
    355   return sizeof(Node) * list.size() +
    356          EstimateIterableMemoryUsage(list);
    357 }
    358 
    359 template <class T>
    360 size_t EstimateMemoryUsage(const base::LinkedList<T>& list) {
    361   size_t memory_usage = 0u;
    362   for (base::LinkNode<T>* node = list.head(); node != list.end();
    363        node = node->next()) {
    364     // Since we increment by calling node = node->next() we know that node
    365     // isn't nullptr.
    366     memory_usage += EstimateMemoryUsage(*node->value()) + sizeof(T);
    367   }
    368   return memory_usage;
    369 }
    370 
    371 // Tree containers
    372 
    373 template <class V>
    374 size_t EstimateTreeMemoryUsage(size_t size) {
    375   // Tree containers are modeled after libc++
    376   // (__tree_node from include/__tree)
    377   struct Node {
    378     Node* left;
    379     Node* right;
    380     Node* parent;
    381     bool is_black;
    382     V value;
    383   };
    384   return sizeof(Node) * size;
    385 }
    386 
    387 template <class T, class C, class A>
    388 size_t EstimateMemoryUsage(const std::set<T, C, A>& set) {
    389   using value_type = typename std::set<T, C, A>::value_type;
    390   return EstimateTreeMemoryUsage<value_type>(set.size()) +
    391          EstimateIterableMemoryUsage(set);
    392 }
    393 
    394 template <class T, class C, class A>
    395 size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set) {
    396   using value_type = typename std::multiset<T, C, A>::value_type;
    397   return EstimateTreeMemoryUsage<value_type>(set.size()) +
    398          EstimateIterableMemoryUsage(set);
    399 }
    400 
    401 template <class K, class V, class C, class A>
    402 size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map) {
    403   using value_type = typename std::map<K, V, C, A>::value_type;
    404   return EstimateTreeMemoryUsage<value_type>(map.size()) +
    405          EstimateIterableMemoryUsage(map);
    406 }
    407 
    408 template <class K, class V, class C, class A>
    409 size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map) {
    410   using value_type = typename std::multimap<K, V, C, A>::value_type;
    411   return EstimateTreeMemoryUsage<value_type>(map.size()) +
    412          EstimateIterableMemoryUsage(map);
    413 }
    414 
    415 // HashMap containers
    416 
    417 namespace internal {
    418 
    419 // While hashtable containers model doesn't depend on STL implementation, one
    420 // detail still crept in: bucket_count. It's used in size estimation, but its
    421 // value after inserting N items is not predictable.
    422 // This function is specialized by unittests to return constant value, thus
    423 // excluding bucket_count from testing.
    424 template <class V>
    425 size_t HashMapBucketCountForTesting(size_t bucket_count) {
    426   return bucket_count;
    427 }
    428 
    429 }  // namespace internal
    430 
    431 template <class V>
    432 size_t EstimateHashMapMemoryUsage(size_t bucket_count, size_t size) {
    433   // Hashtable containers are modeled after libc++
    434   // (__hash_node from include/__hash_table)
    435   struct Node {
    436     void* next;
    437     size_t hash;
    438     V value;
    439   };
    440   using Bucket = void*;
    441   bucket_count = internal::HashMapBucketCountForTesting<V>(bucket_count);
    442   return sizeof(Bucket) * bucket_count + sizeof(Node) * size;
    443 }
    444 
    445 template <class K, class H, class KE, class A>
    446 size_t EstimateMemoryUsage(const std::unordered_set<K, H, KE, A>& set) {
    447   using value_type = typename std::unordered_set<K, H, KE, A>::value_type;
    448   return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(),
    449                                                 set.size()) +
    450          EstimateIterableMemoryUsage(set);
    451 }
    452 
    453 template <class K, class H, class KE, class A>
    454 size_t EstimateMemoryUsage(const std::unordered_multiset<K, H, KE, A>& set) {
    455   using value_type = typename std::unordered_multiset<K, H, KE, A>::value_type;
    456   return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(),
    457                                                 set.size()) +
    458          EstimateIterableMemoryUsage(set);
    459 }
    460 
    461 template <class K, class V, class H, class KE, class A>
    462 size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map) {
    463   using value_type = typename std::unordered_map<K, V, H, KE, A>::value_type;
    464   return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(),
    465                                                 map.size()) +
    466          EstimateIterableMemoryUsage(map);
    467 }
    468 
    469 template <class K, class V, class H, class KE, class A>
    470 size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map) {
    471   using value_type =
    472       typename std::unordered_multimap<K, V, H, KE, A>::value_type;
    473   return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(),
    474                                                 map.size()) +
    475          EstimateIterableMemoryUsage(map);
    476 }
    477 
    478 // std::deque
    479 
    480 template <class T, class A>
    481 size_t EstimateMemoryUsage(const std::deque<T, A>& deque) {
    482 // Since std::deque implementations are wildly different
    483 // (see crbug.com/674287), we can't have one "good enough"
    484 // way to estimate.
    485 
    486 // kBlockSize      - minimum size of a block, in bytes
    487 // kMinBlockLength - number of elements in a block
    488 //                   if sizeof(T) > kBlockSize
    489 #if defined(_LIBCPP_VERSION)
    490   size_t kBlockSize = 4096;
    491   size_t kMinBlockLength = 16;
    492 #elif defined(__GLIBCXX__)
    493   size_t kBlockSize = 512;
    494   size_t kMinBlockLength = 1;
    495 #elif defined(_MSC_VER)
    496   size_t kBlockSize = 16;
    497   size_t kMinBlockLength = 1;
    498 #else
    499   size_t kBlockSize = 0;
    500   size_t kMinBlockLength = 1;
    501 #endif
    502 
    503   size_t block_length =
    504       (sizeof(T) > kBlockSize) ? kMinBlockLength : kBlockSize / sizeof(T);
    505 
    506   size_t blocks = (deque.size() + block_length - 1) / block_length;
    507 
    508 #if defined(__GLIBCXX__)
    509   // libstdc++: deque always has at least one block
    510   if (!blocks)
    511     blocks = 1;
    512 #endif
    513 
    514 #if defined(_LIBCPP_VERSION)
    515   // libc++: deque keeps at most two blocks when it shrinks,
    516   // so even if the size is zero, deque might be holding up
    517   // to 4096 * 2 bytes. One way to know whether deque has
    518   // ever allocated (and hence has 1 or 2 blocks) is to check
    519   // iterator's pointer. Non-zero value means that deque has
    520   // at least one block.
    521   if (!blocks && deque.begin().operator->())
    522     blocks = 1;
    523 #endif
    524 
    525   return (blocks * block_length * sizeof(T)) +
    526          EstimateIterableMemoryUsage(deque);
    527 }
    528 
    529 // Container adapters
    530 
    531 template <class T, class C>
    532 size_t EstimateMemoryUsage(const std::queue<T, C>& queue) {
    533   return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue));
    534 }
    535 
    536 template <class T, class C>
    537 size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue) {
    538   return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue));
    539 }
    540 
    541 template <class T, class C>
    542 size_t EstimateMemoryUsage(const std::stack<T, C>& stack) {
    543   return EstimateMemoryUsage(internal::GetUnderlyingContainer(stack));
    544 }
    545 
    546 }  // namespace trace_event
    547 }  // namespace base
    548 
    549 #endif  // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
    550