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