cache_entry_stats.h 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
  2. // This source code is licensed under both the GPLv2 (found in the
  3. // COPYING file in the root directory) and Apache 2.0 License
  4. // (found in the LICENSE.Apache file in the root directory).
  5. #pragma once
  6. #include <array>
  7. #include <cstdint>
  8. #include <memory>
  9. #include <mutex>
  10. #include "cache/cache_key.h"
  11. #include "cache/typed_cache.h"
  12. #include "port/lang.h"
  13. #include "rocksdb/cache.h"
  14. #include "rocksdb/status.h"
  15. #include "rocksdb/system_clock.h"
  16. #include "test_util/sync_point.h"
  17. #include "util/coding_lean.h"
  18. namespace ROCKSDB_NAMESPACE {
  19. // A generic helper object for gathering stats about cache entries by
  20. // iterating over them with ApplyToAllEntries. This class essentially
  21. // solves the problem of slowing down a Cache with too many stats
  22. // collectors that could be sharing stat results, such as from multiple
  23. // column families or multiple DBs sharing a Cache. We employ a few
  24. // mitigations:
  25. // * Only one collector for a particular kind of Stats is alive
  26. // for each Cache. This is guaranteed using the Cache itself to hold
  27. // the collector.
  28. // * A mutex ensures only one thread is gathering stats for this
  29. // collector.
  30. // * The most recent gathered stats are saved and simply copied to
  31. // satisfy requests within a time window (default: 3 minutes) of
  32. // completion of the most recent stat gathering.
  33. //
  34. // Template parameter Stats must be copyable and trivially constructable,
  35. // as well as...
  36. // concept Stats {
  37. // // Notification before applying callback to all entries
  38. // void BeginCollection(Cache*, SystemClock*, uint64_t start_time_micros);
  39. // // Get the callback to apply to all entries. `callback`
  40. // // type must be compatible with Cache::ApplyToAllEntries
  41. // callback GetEntryCallback();
  42. // // Notification after applying callback to all entries
  43. // void EndCollection(Cache*, SystemClock*, uint64_t end_time_micros);
  44. // // Notification that a collection was skipped because of
  45. // // sufficiently recent saved results.
  46. // void SkippedCollection();
  47. // }
  48. template <class Stats>
  49. class CacheEntryStatsCollector {
  50. public:
  51. // Gather and save stats if saved stats are too old. (Use GetStats() to
  52. // read saved stats.)
  53. //
  54. // Maximum allowed age for a "hit" on saved results is determined by the
  55. // two interval parameters. Both set to 0 forces a re-scan. For example
  56. // with min_interval_seconds=300 and min_interval_factor=100, if the last
  57. // scan took 10s, we would only rescan ("miss") if the age in seconds of
  58. // the saved results is > max(300, 100*10).
  59. // Justification: scans can vary wildly in duration, e.g. from 0.02 sec
  60. // to as much as 20 seconds, so we want to be able to cap the absolute
  61. // and relative frequency of scans.
  62. void CollectStats(int min_interval_seconds, int min_interval_factor) {
  63. // Waits for any pending reader or writer (collector)
  64. std::lock_guard<std::mutex> lock(working_mutex_);
  65. uint64_t max_age_micros =
  66. static_cast<uint64_t>(std::max(min_interval_seconds, 0)) * 1000000U;
  67. if (last_end_time_micros_ > last_start_time_micros_ &&
  68. min_interval_factor > 0) {
  69. max_age_micros = std::max(
  70. max_age_micros, min_interval_factor * (last_end_time_micros_ -
  71. last_start_time_micros_));
  72. }
  73. uint64_t start_time_micros = clock_->NowMicros();
  74. if ((start_time_micros - last_end_time_micros_) > max_age_micros) {
  75. last_start_time_micros_ = start_time_micros;
  76. working_stats_.BeginCollection(cache_, clock_, start_time_micros);
  77. cache_->ApplyToAllEntries(working_stats_.GetEntryCallback(), {});
  78. TEST_SYNC_POINT_CALLBACK(
  79. "CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries", nullptr);
  80. uint64_t end_time_micros = clock_->NowMicros();
  81. last_end_time_micros_ = end_time_micros;
  82. working_stats_.EndCollection(cache_, clock_, end_time_micros);
  83. } else {
  84. working_stats_.SkippedCollection();
  85. }
  86. // Save so that we don't need to wait for an outstanding collection in
  87. // order to make of copy of the last saved stats
  88. std::lock_guard<std::mutex> lock2(saved_mutex_);
  89. saved_stats_ = working_stats_;
  90. }
  91. // Gets saved stats, regardless of age
  92. void GetStats(Stats *stats) {
  93. std::lock_guard<std::mutex> lock(saved_mutex_);
  94. *stats = saved_stats_;
  95. }
  96. Cache *GetCache() const { return cache_; }
  97. // Gets or creates a shared instance of CacheEntryStatsCollector in the
  98. // cache itself, and saves into `ptr`. This shared_ptr will hold the
  99. // entry in cache until all refs are destroyed.
  100. static Status GetShared(Cache *raw_cache, SystemClock *clock,
  101. std::shared_ptr<CacheEntryStatsCollector> *ptr) {
  102. assert(raw_cache);
  103. BasicTypedCacheInterface<CacheEntryStatsCollector, CacheEntryRole::kMisc>
  104. cache{raw_cache};
  105. const Slice &cache_key = GetCacheKey();
  106. auto h = cache.Lookup(cache_key);
  107. if (h == nullptr) {
  108. // Not yet in cache, but Cache doesn't provide a built-in way to
  109. // avoid racing insert. So we double-check under a shared mutex,
  110. // inspired by TableCache.
  111. STATIC_AVOID_DESTRUCTION(std::mutex, static_mutex);
  112. std::lock_guard<std::mutex> lock(static_mutex);
  113. h = cache.Lookup(cache_key);
  114. if (h == nullptr) {
  115. auto new_ptr = new CacheEntryStatsCollector(cache.get(), clock);
  116. // TODO: non-zero charge causes some tests that count block cache
  117. // usage to go flaky. Fix the problem somehow so we can use an
  118. // accurate charge.
  119. size_t charge = 0;
  120. Status s =
  121. cache.Insert(cache_key, new_ptr, charge, &h, Cache::Priority::HIGH);
  122. if (!s.ok()) {
  123. assert(h == nullptr);
  124. delete new_ptr;
  125. return s;
  126. }
  127. }
  128. }
  129. // If we reach here, shared entry is in cache with handle `h`.
  130. assert(cache.get()->GetCacheItemHelper(h) == cache.GetBasicHelper());
  131. // Build an aliasing shared_ptr that keeps `ptr` in cache while there
  132. // are references.
  133. *ptr = cache.SharedGuard(h);
  134. return Status::OK();
  135. }
  136. private:
  137. explicit CacheEntryStatsCollector(Cache *cache, SystemClock *clock)
  138. : saved_stats_(),
  139. working_stats_(),
  140. last_start_time_micros_(0),
  141. last_end_time_micros_(/*pessimistic*/ 10000000),
  142. cache_(cache),
  143. clock_(clock) {}
  144. static const Slice &GetCacheKey() {
  145. // For each template instantiation
  146. static CacheKey ckey = CacheKey::CreateUniqueForProcessLifetime();
  147. static Slice ckey_slice = ckey.AsSlice();
  148. return ckey_slice;
  149. }
  150. std::mutex saved_mutex_;
  151. Stats saved_stats_;
  152. std::mutex working_mutex_;
  153. Stats working_stats_;
  154. uint64_t last_start_time_micros_;
  155. uint64_t last_end_time_micros_;
  156. Cache *const cache_;
  157. SystemClock *const clock_;
  158. };
  159. } // namespace ROCKSDB_NAMESPACE