]>
Commit | Line | Data |
---|---|---|
7c673cae | 1 | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
11fdf7f2 TL |
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). | |
7c673cae FG |
5 | |
6 | #include "rocksdb/utilities/sim_cache.h" | |
7 | #include <atomic> | |
8 | #include "monitoring/statistics.h" | |
9 | #include "port/port.h" | |
11fdf7f2 TL |
10 | #include "rocksdb/env.h" |
11 | #include "util/file_reader_writer.h" | |
12 | #include "util/mutexlock.h" | |
13 | #include "util/string_util.h" | |
7c673cae FG |
14 | |
15 | namespace rocksdb { | |
16 | ||
17 | namespace { | |
11fdf7f2 TL |
18 | |
19 | class CacheActivityLogger { | |
20 | public: | |
21 | CacheActivityLogger() | |
22 | : activity_logging_enabled_(false), max_logging_size_(0) {} | |
23 | ||
24 | ~CacheActivityLogger() { | |
25 | MutexLock l(&mutex_); | |
26 | ||
27 | StopLoggingInternal(); | |
28 | } | |
29 | ||
30 | Status StartLogging(const std::string& activity_log_file, Env* env, | |
31 | uint64_t max_logging_size = 0) { | |
32 | assert(activity_log_file != ""); | |
33 | assert(env != nullptr); | |
34 | ||
35 | Status status; | |
36 | EnvOptions env_opts; | |
37 | std::unique_ptr<WritableFile> log_file; | |
38 | ||
39 | MutexLock l(&mutex_); | |
40 | ||
41 | // Stop existing logging if any | |
42 | StopLoggingInternal(); | |
43 | ||
44 | // Open log file | |
45 | status = env->NewWritableFile(activity_log_file, &log_file, env_opts); | |
46 | if (!status.ok()) { | |
47 | return status; | |
48 | } | |
49 | file_writer_.reset(new WritableFileWriter(std::move(log_file), | |
50 | activity_log_file, env_opts)); | |
51 | ||
52 | max_logging_size_ = max_logging_size; | |
53 | activity_logging_enabled_.store(true); | |
54 | ||
55 | return status; | |
56 | } | |
57 | ||
58 | void StopLogging() { | |
59 | MutexLock l(&mutex_); | |
60 | ||
61 | StopLoggingInternal(); | |
62 | } | |
63 | ||
64 | void ReportLookup(const Slice& key) { | |
65 | if (activity_logging_enabled_.load() == false) { | |
66 | return; | |
67 | } | |
68 | ||
69 | std::string log_line = "LOOKUP - " + key.ToString(true) + "\n"; | |
70 | ||
71 | // line format: "LOOKUP - <KEY>" | |
72 | MutexLock l(&mutex_); | |
73 | Status s = file_writer_->Append(log_line); | |
74 | if (!s.ok() && bg_status_.ok()) { | |
75 | bg_status_ = s; | |
76 | } | |
77 | if (MaxLoggingSizeReached() || !bg_status_.ok()) { | |
78 | // Stop logging if we have reached the max file size or | |
79 | // encountered an error | |
80 | StopLoggingInternal(); | |
81 | } | |
82 | } | |
83 | ||
84 | void ReportAdd(const Slice& key, size_t size) { | |
85 | if (activity_logging_enabled_.load() == false) { | |
86 | return; | |
87 | } | |
88 | ||
89 | std::string log_line = "ADD - "; | |
90 | log_line += key.ToString(true); | |
91 | log_line += " - "; | |
92 | AppendNumberTo(&log_line, size); | |
93 | // @lint-ignore TXT2 T25377293 Grandfathered in | |
94 | log_line += "\n"; | |
95 | ||
96 | // line format: "ADD - <KEY> - <KEY-SIZE>" | |
97 | MutexLock l(&mutex_); | |
98 | Status s = file_writer_->Append(log_line); | |
99 | if (!s.ok() && bg_status_.ok()) { | |
100 | bg_status_ = s; | |
101 | } | |
102 | ||
103 | if (MaxLoggingSizeReached() || !bg_status_.ok()) { | |
104 | // Stop logging if we have reached the max file size or | |
105 | // encountered an error | |
106 | StopLoggingInternal(); | |
107 | } | |
108 | } | |
109 | ||
110 | Status& bg_status() { | |
111 | MutexLock l(&mutex_); | |
112 | return bg_status_; | |
113 | } | |
114 | ||
115 | private: | |
116 | bool MaxLoggingSizeReached() { | |
117 | mutex_.AssertHeld(); | |
118 | ||
119 | return (max_logging_size_ > 0 && | |
120 | file_writer_->GetFileSize() >= max_logging_size_); | |
121 | } | |
122 | ||
123 | void StopLoggingInternal() { | |
124 | mutex_.AssertHeld(); | |
125 | ||
126 | if (!activity_logging_enabled_) { | |
127 | return; | |
128 | } | |
129 | ||
130 | activity_logging_enabled_.store(false); | |
131 | Status s = file_writer_->Close(); | |
132 | if (!s.ok() && bg_status_.ok()) { | |
133 | bg_status_ = s; | |
134 | } | |
135 | } | |
136 | ||
137 | // Mutex to sync writes to file_writer, and all following | |
138 | // class data members | |
139 | port::Mutex mutex_; | |
140 | // Indicates if logging is currently enabled | |
141 | // atomic to allow reads without mutex | |
142 | std::atomic<bool> activity_logging_enabled_; | |
143 | // When reached, we will stop logging and close the file | |
144 | // Value of 0 means unlimited | |
145 | uint64_t max_logging_size_; | |
146 | std::unique_ptr<WritableFileWriter> file_writer_; | |
147 | Status bg_status_; | |
148 | }; | |
149 | ||
7c673cae FG |
150 | // SimCacheImpl definition |
151 | class SimCacheImpl : public SimCache { | |
152 | public: | |
153 | // capacity for real cache (ShardedLRUCache) | |
154 | // test_capacity for key only cache | |
155 | SimCacheImpl(std::shared_ptr<Cache> cache, size_t sim_capacity, | |
156 | int num_shard_bits) | |
157 | : cache_(cache), | |
158 | key_only_cache_(NewLRUCache(sim_capacity, num_shard_bits)), | |
159 | miss_times_(0), | |
11fdf7f2 TL |
160 | hit_times_(0), |
161 | stats_(nullptr) {} | |
7c673cae FG |
162 | |
163 | virtual ~SimCacheImpl() {} | |
164 | virtual void SetCapacity(size_t capacity) override { | |
165 | cache_->SetCapacity(capacity); | |
166 | } | |
167 | ||
168 | virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override { | |
169 | cache_->SetStrictCapacityLimit(strict_capacity_limit); | |
170 | } | |
171 | ||
172 | virtual Status Insert(const Slice& key, void* value, size_t charge, | |
173 | void (*deleter)(const Slice& key, void* value), | |
174 | Handle** handle, Priority priority) override { | |
175 | // The handle and value passed in are for real cache, so we pass nullptr | |
176 | // to key_only_cache_ for both instead. Also, the deleter function pointer | |
177 | // will be called by user to perform some external operation which should | |
178 | // be applied only once. Thus key_only_cache accepts an empty function. | |
179 | // *Lambda function without capture can be assgined to a function pointer | |
180 | Handle* h = key_only_cache_->Lookup(key); | |
181 | if (h == nullptr) { | |
182 | key_only_cache_->Insert(key, nullptr, charge, | |
11fdf7f2 | 183 | [](const Slice& /*k*/, void* /*v*/) {}, nullptr, |
7c673cae FG |
184 | priority); |
185 | } else { | |
186 | key_only_cache_->Release(h); | |
187 | } | |
11fdf7f2 TL |
188 | |
189 | cache_activity_logger_.ReportAdd(key, charge); | |
190 | ||
7c673cae FG |
191 | return cache_->Insert(key, value, charge, deleter, handle, priority); |
192 | } | |
193 | ||
194 | virtual Handle* Lookup(const Slice& key, Statistics* stats) override { | |
195 | Handle* h = key_only_cache_->Lookup(key); | |
196 | if (h != nullptr) { | |
197 | key_only_cache_->Release(h); | |
198 | inc_hit_counter(); | |
199 | RecordTick(stats, SIM_BLOCK_CACHE_HIT); | |
200 | } else { | |
201 | inc_miss_counter(); | |
202 | RecordTick(stats, SIM_BLOCK_CACHE_MISS); | |
203 | } | |
11fdf7f2 TL |
204 | |
205 | cache_activity_logger_.ReportLookup(key); | |
206 | ||
7c673cae FG |
207 | return cache_->Lookup(key, stats); |
208 | } | |
209 | ||
210 | virtual bool Ref(Handle* handle) override { return cache_->Ref(handle); } | |
211 | ||
212 | virtual bool Release(Handle* handle, bool force_erase = false) override { | |
213 | return cache_->Release(handle, force_erase); | |
214 | } | |
215 | ||
216 | virtual void Erase(const Slice& key) override { | |
217 | cache_->Erase(key); | |
218 | key_only_cache_->Erase(key); | |
219 | } | |
220 | ||
221 | virtual void* Value(Handle* handle) override { return cache_->Value(handle); } | |
222 | ||
223 | virtual uint64_t NewId() override { return cache_->NewId(); } | |
224 | ||
225 | virtual size_t GetCapacity() const override { return cache_->GetCapacity(); } | |
226 | ||
227 | virtual bool HasStrictCapacityLimit() const override { | |
228 | return cache_->HasStrictCapacityLimit(); | |
229 | } | |
230 | ||
231 | virtual size_t GetUsage() const override { return cache_->GetUsage(); } | |
232 | ||
233 | virtual size_t GetUsage(Handle* handle) const override { | |
234 | return cache_->GetUsage(handle); | |
235 | } | |
236 | ||
237 | virtual size_t GetPinnedUsage() const override { | |
238 | return cache_->GetPinnedUsage(); | |
239 | } | |
240 | ||
241 | virtual void DisownData() override { | |
242 | cache_->DisownData(); | |
243 | key_only_cache_->DisownData(); | |
244 | } | |
245 | ||
246 | virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t), | |
247 | bool thread_safe) override { | |
248 | // only apply to _cache since key_only_cache doesn't hold value | |
249 | cache_->ApplyToAllCacheEntries(callback, thread_safe); | |
250 | } | |
251 | ||
252 | virtual void EraseUnRefEntries() override { | |
253 | cache_->EraseUnRefEntries(); | |
254 | key_only_cache_->EraseUnRefEntries(); | |
255 | } | |
256 | ||
257 | virtual size_t GetSimCapacity() const override { | |
258 | return key_only_cache_->GetCapacity(); | |
259 | } | |
260 | virtual size_t GetSimUsage() const override { | |
261 | return key_only_cache_->GetUsage(); | |
262 | } | |
263 | virtual void SetSimCapacity(size_t capacity) override { | |
264 | key_only_cache_->SetCapacity(capacity); | |
265 | } | |
266 | ||
267 | virtual uint64_t get_miss_counter() const override { | |
268 | return miss_times_.load(std::memory_order_relaxed); | |
269 | } | |
270 | ||
271 | virtual uint64_t get_hit_counter() const override { | |
272 | return hit_times_.load(std::memory_order_relaxed); | |
273 | } | |
274 | ||
275 | virtual void reset_counter() override { | |
276 | miss_times_.store(0, std::memory_order_relaxed); | |
277 | hit_times_.store(0, std::memory_order_relaxed); | |
278 | SetTickerCount(stats_, SIM_BLOCK_CACHE_HIT, 0); | |
279 | SetTickerCount(stats_, SIM_BLOCK_CACHE_MISS, 0); | |
280 | } | |
281 | ||
282 | virtual std::string ToString() const override { | |
283 | std::string res; | |
284 | res.append("SimCache MISSes: " + std::to_string(get_miss_counter()) + "\n"); | |
285 | res.append("SimCache HITs: " + std::to_string(get_hit_counter()) + "\n"); | |
286 | char buff[350]; | |
287 | auto lookups = get_miss_counter() + get_hit_counter(); | |
288 | snprintf(buff, sizeof(buff), "SimCache HITRATE: %.2f%%\n", | |
289 | (lookups == 0 ? 0 : get_hit_counter() * 100.0f / lookups)); | |
290 | res.append(buff); | |
291 | return res; | |
292 | } | |
293 | ||
294 | virtual std::string GetPrintableOptions() const override { | |
295 | std::string ret; | |
296 | ret.reserve(20000); | |
297 | ret.append(" cache_options:\n"); | |
298 | ret.append(cache_->GetPrintableOptions()); | |
299 | ret.append(" sim_cache_options:\n"); | |
300 | ret.append(key_only_cache_->GetPrintableOptions()); | |
301 | return ret; | |
302 | } | |
303 | ||
11fdf7f2 TL |
304 | virtual Status StartActivityLogging(const std::string& activity_log_file, |
305 | Env* env, | |
306 | uint64_t max_logging_size = 0) override { | |
307 | return cache_activity_logger_.StartLogging(activity_log_file, env, | |
308 | max_logging_size); | |
309 | } | |
310 | ||
311 | virtual void StopActivityLogging() override { | |
312 | cache_activity_logger_.StopLogging(); | |
313 | } | |
314 | ||
315 | virtual Status GetActivityLoggingStatus() override { | |
316 | return cache_activity_logger_.bg_status(); | |
317 | } | |
318 | ||
7c673cae FG |
319 | private: |
320 | std::shared_ptr<Cache> cache_; | |
321 | std::shared_ptr<Cache> key_only_cache_; | |
322 | std::atomic<uint64_t> miss_times_; | |
323 | std::atomic<uint64_t> hit_times_; | |
324 | Statistics* stats_; | |
11fdf7f2 TL |
325 | CacheActivityLogger cache_activity_logger_; |
326 | ||
7c673cae FG |
327 | void inc_miss_counter() { |
328 | miss_times_.fetch_add(1, std::memory_order_relaxed); | |
329 | } | |
330 | void inc_hit_counter() { hit_times_.fetch_add(1, std::memory_order_relaxed); } | |
331 | }; | |
332 | ||
333 | } // end anonymous namespace | |
334 | ||
335 | // For instrumentation purpose, use NewSimCache instead | |
336 | std::shared_ptr<SimCache> NewSimCache(std::shared_ptr<Cache> cache, | |
337 | size_t sim_capacity, int num_shard_bits) { | |
338 | if (num_shard_bits >= 20) { | |
339 | return nullptr; // the cache cannot be sharded into too many fine pieces | |
340 | } | |
341 | return std::make_shared<SimCacheImpl>(cache, sim_capacity, num_shard_bits); | |
342 | } | |
343 | ||
344 | } // end namespace rocksdb |