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