1 // Copyright 2016 The Chromium Authors 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/lru_cache.h" 30 #include "base/containers/queue.h" 31 #include "base/memory/raw_ptr.h" 32 #include "base/stl_util.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 K, class V, class C> 182 size_t EstimateMemoryUsage(const base::LRUCache<K, V, C>& lru); 183 184 template <class K, class V, class C> 185 size_t EstimateMemoryUsage(const base::HashingLRUCache<K, V, C>& lru); 186 187 template <class V, class C> 188 size_t EstimateMemoryUsage(const base::LRUCacheSet<V, C>& lru); 189 190 template <class V, class C> 191 size_t EstimateMemoryUsage(const base::HashingLRUCacheSet<V, C>& lru); 192 193 // TODO(dskiba): 194 // std::forward_list 195 196 // Definitions 197 198 namespace internal { 199 200 // HasEMU<T>::value is true iff EstimateMemoryUsage(T) is available. 201 // (This is the default version, which is false.) 202 template <class T, class X = void> 203 struct HasEMU : std::false_type {}; 204 205 // This HasEMU specialization is only picked up if there exists function 206 // EstimateMemoryUsage(const T&) that returns size_t. Simpler ways to 207 // achieve this don't work on MSVC. 208 template <class T> 209 struct HasEMU< 210 T, 211 typename std::enable_if<std::is_same< 212 size_t, 213 decltype(EstimateMemoryUsage(std::declval<const T&>()))>::value>::type> 214 : std::true_type {}; 215 216 // EMUCaller<T> does three things: 217 // 1. Defines Call() method that calls EstimateMemoryUsage(T) if it's 218 // available. 219 // 2. If EstimateMemoryUsage(T) is not available, but T has trivial dtor 220 // (i.e. it's POD, integer, pointer, enum, etc.) then it defines Call() 221 // method that returns 0. This is useful for containers, which allocate 222 // memory regardless of T (also for cases like std::map<int, MyClass>). 223 // 3. Finally, if EstimateMemoryUsage(T) is not available, then it triggers 224 // a static_assert with a helpful message. That cuts numbers of errors 225 // considerably - if you just call EstimateMemoryUsage(T) but it's not 226 // available for T, then compiler will helpfully list *all* possible 227 // variants of it, with an explanation for each. 228 template <class T, class X = void> 229 struct EMUCaller { 230 // std::is_same<> below makes static_assert depend on T, in order to 231 // prevent it from asserting regardless instantiation. 232 static_assert(std::is_same<T, std::false_type>::value, 233 "Neither global function 'size_t EstimateMemoryUsage(T)' " 234 "nor member function 'size_t T::EstimateMemoryUsage() const' " 235 "is defined for the type."); 236 237 static size_t Call(const T&) { return 0; } 238 }; 239 240 template <class T> 241 struct EMUCaller<T, typename std::enable_if<HasEMU<T>::value>::type> { 242 static size_t Call(const T& value) { return EstimateMemoryUsage(value); } 243 }; 244 245 template <template <class...> class Container, class I, class = void> 246 struct IsComplexIteratorForContainer : std::false_type {}; 247 248 template <template <class...> class Container, class I> 249 struct IsComplexIteratorForContainer< 250 Container, 251 I, 252 std::enable_if_t<!std::is_pointer<I>::value && 253 base::internal::is_iterator<I>::value>> { 254 using value_type = typename std::iterator_traits<I>::value_type; 255 using container_type = Container<value_type>; 256 257 // We use enum instead of static constexpr bool, beause we don't have inline 258 // variables until c++17. 259 // 260 // The downside is - value is not of type bool. 261 enum : bool { 262 value = 263 std::is_same<typename container_type::iterator, I>::value || 264 std::is_same<typename container_type::const_iterator, I>::value || 265 std::is_same<typename container_type::reverse_iterator, I>::value || 266 std::is_same<typename container_type::const_reverse_iterator, I>::value, 267 }; 268 }; 269 270 template <class I, template <class...> class... Containers> 271 constexpr bool OneOfContainersComplexIterators() { 272 // We are forced to create a temporary variable to workaround a compilation 273 // error in msvs. 274 const bool all_tests[] = { 275 IsComplexIteratorForContainer<Containers, I>::value...}; 276 for (bool test : all_tests) 277 if (test) 278 return true; 279 return false; 280 } 281 282 // std::array has an extra required template argument. We curry it. 283 template <class T> 284 using array_test_helper = std::array<T, 1>; 285 286 template <class I> 287 constexpr bool IsStandardContainerComplexIterator() { 288 // TODO(dyaroshev): deal with maps iterators if there is a need. 289 // It requires to parse pairs into keys and values. 290 // TODO(dyaroshev): deal with unordered containers: they do not have reverse 291 // iterators. 292 return OneOfContainersComplexIterators< 293 I, array_test_helper, std::vector, std::deque, 294 /*std::forward_list,*/ std::list, std::set, std::multiset>(); 295 } 296 297 // Work around MSVS bug. For some reason constexpr function doesn't work. 298 // However variable template does. 299 template <typename T> 300 constexpr bool IsKnownNonAllocatingType_v = 301 std::is_trivially_destructible<T>::value || 302 IsStandardContainerComplexIterator<T>(); 303 304 template <class T> 305 struct EMUCaller< 306 T, 307 std::enable_if_t<!HasEMU<T>::value && IsKnownNonAllocatingType_v<T>>> { 308 static size_t Call(const T& value) { return 0; } 309 }; 310 311 } // namespace internal 312 313 // Proxy that deducts T and calls EMUCaller<T>. 314 // To be used by EstimateMemoryUsage() implementations for containers. 315 template <class T> 316 size_t EstimateItemMemoryUsage(const T& value) { 317 return internal::EMUCaller<T>::Call(value); 318 } 319 320 template <class I> 321 size_t EstimateIterableMemoryUsage(const I& iterable) { 322 size_t memory_usage = 0; 323 for (const auto& item : iterable) { 324 memory_usage += EstimateItemMemoryUsage(item); 325 } 326 return memory_usage; 327 } 328 329 // Global EstimateMemoryUsage(T) that just calls T::EstimateMemoryUsage(). 330 template <class T> 331 auto EstimateMemoryUsage(const T& object) 332 -> decltype(object.EstimateMemoryUsage()) { 333 static_assert( 334 std::is_same<decltype(object.EstimateMemoryUsage()), size_t>::value, 335 "'T::EstimateMemoryUsage() const' must return size_t."); 336 return object.EstimateMemoryUsage(); 337 } 338 339 // String 340 341 template <class C, class T, class A> 342 size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string) { 343 using string_type = std::basic_string<C, T, A>; 344 using value_type = typename string_type::value_type; 345 // C++11 doesn't leave much room for implementors - std::string can 346 // use short string optimization, but that's about it. We detect SSO 347 // by checking that c_str() points inside |string|. 348 const uint8_t* cstr = reinterpret_cast<const uint8_t*>(string.c_str()); 349 const uint8_t* inline_cstr = reinterpret_cast<const uint8_t*>(&string); 350 if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) { 351 // SSO string 352 return 0; 353 } 354 return (string.capacity() + 1) * sizeof(value_type); 355 } 356 357 // Use explicit instantiations from the .cc file (reduces bloat). 358 extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::string&); 359 extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::u16string&); 360 361 // Arrays 362 363 template <class T, size_t N> 364 size_t EstimateMemoryUsage(const std::array<T, N>& array) { 365 return EstimateIterableMemoryUsage(array); 366 } 367 368 template <class T, size_t N> 369 size_t EstimateMemoryUsage(T (&array)[N]) { 370 return EstimateIterableMemoryUsage(array); 371 } 372 373 template <class T> 374 size_t EstimateMemoryUsage(const T* array, size_t array_length) { 375 size_t memory_usage = sizeof(T) * array_length; 376 for (size_t i = 0; i != array_length; ++i) { 377 memory_usage += EstimateItemMemoryUsage(array[i]); 378 } 379 return memory_usage; 380 } 381 382 // std::unique_ptr 383 384 template <class T, class D> 385 size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr) { 386 return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0; 387 } 388 389 template <class T, class D> 390 size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array, 391 size_t array_length) { 392 return EstimateMemoryUsage(array.get(), array_length); 393 } 394 395 // std::shared_ptr 396 397 template <class T> 398 size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr) { 399 auto use_count = ptr.use_count(); 400 if (use_count == 0) { 401 return 0; 402 } 403 // Model shared_ptr after libc++, 404 // see __shared_ptr_pointer from include/memory 405 struct SharedPointer { 406 raw_ptr<void> vtbl; 407 long shared_owners; 408 long shared_weak_owners; 409 raw_ptr<T> value; 410 }; 411 // If object of size S shared N > S times we prefer to (potentially) 412 // overestimate than to return 0. 413 return sizeof(SharedPointer) + 414 (EstimateItemMemoryUsage(*ptr) + (use_count - 1)) / use_count; 415 } 416 417 // std::pair 418 419 template <class F, class S> 420 size_t EstimateMemoryUsage(const std::pair<F, S>& pair) { 421 return EstimateItemMemoryUsage(pair.first) + 422 EstimateItemMemoryUsage(pair.second); 423 } 424 425 // std::vector 426 427 template <class T, class A> 428 size_t EstimateMemoryUsage(const std::vector<T, A>& vector) { 429 return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector); 430 } 431 432 // std::list 433 434 template <class T, class A> 435 size_t EstimateMemoryUsage(const std::list<T, A>& list) { 436 using value_type = typename std::list<T, A>::value_type; 437 struct Node { 438 raw_ptr<Node> prev; 439 raw_ptr<Node> next; 440 value_type value; 441 }; 442 return sizeof(Node) * list.size() + 443 EstimateIterableMemoryUsage(list); 444 } 445 446 template <class T> 447 size_t EstimateMemoryUsage(const base::LinkedList<T>& list) { 448 size_t memory_usage = 0u; 449 for (base::LinkNode<T>* node = list.head(); node != list.end(); 450 node = node->next()) { 451 // Since we increment by calling node = node->next() we know that node 452 // isn't nullptr. 453 memory_usage += EstimateMemoryUsage(*node->value()) + sizeof(T); 454 } 455 return memory_usage; 456 } 457 458 // Tree containers 459 460 template <class V> 461 size_t EstimateTreeMemoryUsage(size_t size) { 462 // Tree containers are modeled after libc++ 463 // (__tree_node from include/__tree) 464 struct Node { 465 raw_ptr<Node> left; 466 raw_ptr<Node> right; 467 raw_ptr<Node> parent; 468 bool is_black; 469 V value; 470 }; 471 return sizeof(Node) * size; 472 } 473 474 template <class T, class C, class A> 475 size_t EstimateMemoryUsage(const std::set<T, C, A>& set) { 476 using value_type = typename std::set<T, C, A>::value_type; 477 return EstimateTreeMemoryUsage<value_type>(set.size()) + 478 EstimateIterableMemoryUsage(set); 479 } 480 481 template <class T, class C, class A> 482 size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set) { 483 using value_type = typename std::multiset<T, C, A>::value_type; 484 return EstimateTreeMemoryUsage<value_type>(set.size()) + 485 EstimateIterableMemoryUsage(set); 486 } 487 488 template <class K, class V, class C, class A> 489 size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map) { 490 using value_type = typename std::map<K, V, C, A>::value_type; 491 return EstimateTreeMemoryUsage<value_type>(map.size()) + 492 EstimateIterableMemoryUsage(map); 493 } 494 495 template <class K, class V, class C, class A> 496 size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map) { 497 using value_type = typename std::multimap<K, V, C, A>::value_type; 498 return EstimateTreeMemoryUsage<value_type>(map.size()) + 499 EstimateIterableMemoryUsage(map); 500 } 501 502 // HashMap containers 503 504 namespace internal { 505 506 // While hashtable containers model doesn't depend on STL implementation, one 507 // detail still crept in: bucket_count. It's used in size estimation, but its 508 // value after inserting N items is not predictable. 509 // This function is specialized by unittests to return constant value, thus 510 // excluding bucket_count from testing. 511 template <class V> 512 size_t HashMapBucketCountForTesting(size_t bucket_count) { 513 return bucket_count; 514 } 515 516 template <class LruCacheType> 517 size_t DoEstimateMemoryUsageForLruCache(const LruCacheType& lru_cache) { 518 return EstimateMemoryUsage(lru_cache.ordering_) + 519 EstimateMemoryUsage(lru_cache.index_); 520 } 521 522 } // namespace internal 523 524 template <class V> 525 size_t EstimateHashMapMemoryUsage(size_t bucket_count, size_t size) { 526 // Hashtable containers are modeled after libc++ 527 // (__hash_node from include/__hash_table) 528 struct Node { 529 raw_ptr<void> next; 530 size_t hash; 531 V value; 532 }; 533 using Bucket = void*; 534 bucket_count = internal::HashMapBucketCountForTesting<V>(bucket_count); 535 return sizeof(Bucket) * bucket_count + sizeof(Node) * size; 536 } 537 538 template <class K, class H, class KE, class A> 539 size_t EstimateMemoryUsage(const std::unordered_set<K, H, KE, A>& set) { 540 using value_type = typename std::unordered_set<K, H, KE, A>::value_type; 541 return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(), 542 set.size()) + 543 EstimateIterableMemoryUsage(set); 544 } 545 546 template <class K, class H, class KE, class A> 547 size_t EstimateMemoryUsage(const std::unordered_multiset<K, H, KE, A>& set) { 548 using value_type = typename std::unordered_multiset<K, H, KE, A>::value_type; 549 return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(), 550 set.size()) + 551 EstimateIterableMemoryUsage(set); 552 } 553 554 template <class K, class V, class H, class KE, class A> 555 size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map) { 556 using value_type = typename std::unordered_map<K, V, H, KE, A>::value_type; 557 return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(), 558 map.size()) + 559 EstimateIterableMemoryUsage(map); 560 } 561 562 template <class K, class V, class H, class KE, class A> 563 size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map) { 564 using value_type = 565 typename std::unordered_multimap<K, V, H, KE, A>::value_type; 566 return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(), 567 map.size()) + 568 EstimateIterableMemoryUsage(map); 569 } 570 571 // std::deque 572 573 template <class T, class A> 574 size_t EstimateMemoryUsage(const std::deque<T, A>& deque) { 575 // Since std::deque implementations are wildly different 576 // (see crbug.com/674287), we can't have one "good enough" 577 // way to estimate. 578 579 // kBlockSize - minimum size of a block, in bytes 580 // kMinBlockLength - number of elements in a block 581 // if sizeof(T) > kBlockSize 582 #if defined(_LIBCPP_VERSION) 583 size_t kBlockSize = 4096; 584 size_t kMinBlockLength = 16; 585 #elif defined(__GLIBCXX__) 586 size_t kBlockSize = 512; 587 size_t kMinBlockLength = 1; 588 #elif defined(_MSC_VER) 589 size_t kBlockSize = 16; 590 size_t kMinBlockLength = 1; 591 #else 592 size_t kBlockSize = 0; 593 size_t kMinBlockLength = 1; 594 #endif 595 596 size_t block_length = 597 (sizeof(T) > kBlockSize) ? kMinBlockLength : kBlockSize / sizeof(T); 598 599 size_t blocks = (deque.size() + block_length - 1) / block_length; 600 601 #if defined(__GLIBCXX__) 602 // libstdc++: deque always has at least one block 603 if (!blocks) 604 blocks = 1; 605 #endif 606 607 #if defined(_LIBCPP_VERSION) 608 // libc++: deque keeps at most two blocks when it shrinks, 609 // so even if the size is zero, deque might be holding up 610 // to 4096 * 2 bytes. One way to know whether deque has 611 // ever allocated (and hence has 1 or 2 blocks) is to check 612 // iterator's pointer. Non-zero value means that deque has 613 // at least one block. 614 if (!blocks && deque.begin().operator->()) 615 blocks = 1; 616 #endif 617 618 return (blocks * block_length * sizeof(T)) + 619 EstimateIterableMemoryUsage(deque); 620 } 621 622 // Container adapters 623 624 template <class T, class C> 625 size_t EstimateMemoryUsage(const std::queue<T, C>& queue) { 626 return EstimateMemoryUsage(GetUnderlyingContainer(queue)); 627 } 628 629 template <class T, class C> 630 size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue) { 631 return EstimateMemoryUsage(GetUnderlyingContainer(queue)); 632 } 633 634 template <class T, class C> 635 size_t EstimateMemoryUsage(const std::stack<T, C>& stack) { 636 return EstimateMemoryUsage(GetUnderlyingContainer(stack)); 637 } 638 639 // base::circular_deque 640 641 template <class T> 642 size_t EstimateMemoryUsage(const base::circular_deque<T>& deque) { 643 return sizeof(T) * deque.capacity() + EstimateIterableMemoryUsage(deque); 644 } 645 646 // Flat containers 647 648 template <class T, class C> 649 size_t EstimateMemoryUsage(const base::flat_set<T, C>& set) { 650 using value_type = typename base::flat_set<T, C>::value_type; 651 return sizeof(value_type) * set.capacity() + EstimateIterableMemoryUsage(set); 652 } 653 654 template <class K, class V, class C> 655 size_t EstimateMemoryUsage(const base::flat_map<K, V, C>& map) { 656 using value_type = typename base::flat_map<K, V, C>::value_type; 657 return sizeof(value_type) * map.capacity() + EstimateIterableMemoryUsage(map); 658 } 659 660 template <class K, class V, class C> 661 size_t EstimateMemoryUsage(const LRUCache<K, V, C>& lru_cache) { 662 return internal::DoEstimateMemoryUsageForLruCache(lru_cache); 663 } 664 665 template <class K, class V, class C> 666 size_t EstimateMemoryUsage(const HashingLRUCache<K, V, C>& lru_cache) { 667 return internal::DoEstimateMemoryUsageForLruCache(lru_cache); 668 } 669 670 template <class V, class C> 671 size_t EstimateMemoryUsage(const LRUCacheSet<V, C>& lru_cache) { 672 return internal::DoEstimateMemoryUsageForLruCache(lru_cache); 673 } 674 675 template <class V, class C> 676 size_t EstimateMemoryUsage(const HashingLRUCacheSet<V, C>& lru_cache) { 677 return internal::DoEstimateMemoryUsageForLruCache(lru_cache); 678 } 679 680 } // namespace trace_event 681 } // namespace base 682 683 #endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ 684