// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. // This source code is licensed under both the GPLv2 (found in the // COPYING file in the root directory) and Apache 2.0 License // (found in the LICENSE.Apache file in the root directory). #include "rocksdb/utilities/sim_cache.h" #include #include "monitoring/statistics.h" #include "port/port.h" namespace rocksdb { namespace { // SimCacheImpl definition class SimCacheImpl : public SimCache { public: // capacity for real cache (ShardedLRUCache) // test_capacity for key only cache SimCacheImpl(std::shared_ptr cache, size_t sim_capacity, int num_shard_bits) : cache_(cache), key_only_cache_(NewLRUCache(sim_capacity, num_shard_bits)), miss_times_(0), hit_times_(0) {} virtual ~SimCacheImpl() {} virtual void SetCapacity(size_t capacity) override { cache_->SetCapacity(capacity); } virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override { cache_->SetStrictCapacityLimit(strict_capacity_limit); } virtual Status Insert(const Slice& key, void* value, size_t charge, void (*deleter)(const Slice& key, void* value), Handle** handle, Priority priority) override { // The handle and value passed in are for real cache, so we pass nullptr // to key_only_cache_ for both instead. Also, the deleter function pointer // will be called by user to perform some external operation which should // be applied only once. Thus key_only_cache accepts an empty function. // *Lambda function without capture can be assgined to a function pointer Handle* h = key_only_cache_->Lookup(key); if (h == nullptr) { key_only_cache_->Insert(key, nullptr, charge, [](const Slice& k, void* v) {}, nullptr, priority); } else { key_only_cache_->Release(h); } return cache_->Insert(key, value, charge, deleter, handle, priority); } virtual Handle* Lookup(const Slice& key, Statistics* stats) override { Handle* h = key_only_cache_->Lookup(key); if (h != nullptr) { key_only_cache_->Release(h); inc_hit_counter(); RecordTick(stats, SIM_BLOCK_CACHE_HIT); } else { inc_miss_counter(); RecordTick(stats, SIM_BLOCK_CACHE_MISS); } return cache_->Lookup(key, stats); } virtual bool Ref(Handle* handle) override { return cache_->Ref(handle); } virtual bool Release(Handle* handle, bool force_erase = false) override { return cache_->Release(handle, force_erase); } virtual void Erase(const Slice& key) override { cache_->Erase(key); key_only_cache_->Erase(key); } virtual void* Value(Handle* handle) override { return cache_->Value(handle); } virtual uint64_t NewId() override { return cache_->NewId(); } virtual size_t GetCapacity() const override { return cache_->GetCapacity(); } virtual bool HasStrictCapacityLimit() const override { return cache_->HasStrictCapacityLimit(); } virtual size_t GetUsage() const override { return cache_->GetUsage(); } virtual size_t GetUsage(Handle* handle) const override { return cache_->GetUsage(handle); } virtual size_t GetPinnedUsage() const override { return cache_->GetPinnedUsage(); } virtual void DisownData() override { cache_->DisownData(); key_only_cache_->DisownData(); } virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t), bool thread_safe) override { // only apply to _cache since key_only_cache doesn't hold value cache_->ApplyToAllCacheEntries(callback, thread_safe); } virtual void EraseUnRefEntries() override { cache_->EraseUnRefEntries(); key_only_cache_->EraseUnRefEntries(); } virtual size_t GetSimCapacity() const override { return key_only_cache_->GetCapacity(); } virtual size_t GetSimUsage() const override { return key_only_cache_->GetUsage(); } virtual void SetSimCapacity(size_t capacity) override { key_only_cache_->SetCapacity(capacity); } virtual uint64_t get_miss_counter() const override { return miss_times_.load(std::memory_order_relaxed); } virtual uint64_t get_hit_counter() const override { return hit_times_.load(std::memory_order_relaxed); } virtual void reset_counter() override { miss_times_.store(0, std::memory_order_relaxed); hit_times_.store(0, std::memory_order_relaxed); SetTickerCount(stats_, SIM_BLOCK_CACHE_HIT, 0); SetTickerCount(stats_, SIM_BLOCK_CACHE_MISS, 0); } virtual std::string ToString() const override { std::string res; res.append("SimCache MISSes: " + std::to_string(get_miss_counter()) + "\n"); res.append("SimCache HITs: " + std::to_string(get_hit_counter()) + "\n"); char buff[350]; auto lookups = get_miss_counter() + get_hit_counter(); snprintf(buff, sizeof(buff), "SimCache HITRATE: %.2f%%\n", (lookups == 0 ? 0 : get_hit_counter() * 100.0f / lookups)); res.append(buff); return res; } virtual std::string GetPrintableOptions() const override { std::string ret; ret.reserve(20000); ret.append(" cache_options:\n"); ret.append(cache_->GetPrintableOptions()); ret.append(" sim_cache_options:\n"); ret.append(key_only_cache_->GetPrintableOptions()); return ret; } private: std::shared_ptr cache_; std::shared_ptr key_only_cache_; std::atomic miss_times_; std::atomic hit_times_; Statistics* stats_; void inc_miss_counter() { miss_times_.fetch_add(1, std::memory_order_relaxed); } void inc_hit_counter() { hit_times_.fetch_add(1, std::memory_order_relaxed); } }; } // end anonymous namespace // For instrumentation purpose, use NewSimCache instead std::shared_ptr NewSimCache(std::shared_ptr cache, size_t sim_capacity, int num_shard_bits) { if (num_shard_bits >= 20) { return nullptr; // the cache cannot be sharded into too many fine pieces } return std::make_shared(cache, sim_capacity, num_shard_bits); } } // end namespace rocksdb