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