1 //  Copyright (c) 2011-present, Facebook, Inc.  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 #include "utilities/simulator_cache/cache_simulator.h"
7 
8 #include <cstdlib>
9 #include "rocksdb/env.h"
10 #include "test_util/testharness.h"
11 #include "test_util/testutil.h"
12 
13 namespace ROCKSDB_NAMESPACE {
14 namespace {
15 const std::string kBlockKeyPrefix = "test-block-";
16 const std::string kRefKeyPrefix = "test-get-";
17 const std::string kRefKeySequenceNumber = std::string(8, 'c');
18 const uint64_t kGetId = 1;
19 const uint64_t kGetBlockId = 100;
20 const uint64_t kCompactionBlockId = 1000;
21 const uint64_t kCacheSize = 1024 * 1024 * 1024;
22 const uint64_t kGhostCacheSize = 1024 * 1024;
23 }  // namespace
24 
25 class CacheSimulatorTest : public testing::Test {
26  public:
27   const size_t kNumBlocks = 5;
28   const size_t kValueSize = 1000;
29 
CacheSimulatorTest()30   CacheSimulatorTest() { env_ = ROCKSDB_NAMESPACE::Env::Default(); }
31 
GenerateGetRecord(uint64_t getid)32   BlockCacheTraceRecord GenerateGetRecord(uint64_t getid) {
33     BlockCacheTraceRecord record;
34     record.block_type = TraceType::kBlockTraceDataBlock;
35     record.block_size = 4096;
36     record.block_key = kBlockKeyPrefix + std::to_string(kGetBlockId);
37     record.access_timestamp = env_->NowMicros();
38     record.cf_id = 0;
39     record.cf_name = "test";
40     record.caller = TableReaderCaller::kUserGet;
41     record.level = 6;
42     record.sst_fd_number = 0;
43     record.get_id = getid;
44     record.is_cache_hit = Boolean::kFalse;
45     record.no_insert = Boolean::kFalse;
46     record.referenced_key =
47         kRefKeyPrefix + std::to_string(kGetId) + kRefKeySequenceNumber;
48     record.referenced_key_exist_in_block = Boolean::kTrue;
49     record.referenced_data_size = 100;
50     record.num_keys_in_block = 300;
51     return record;
52   }
53 
GenerateCompactionRecord()54   BlockCacheTraceRecord GenerateCompactionRecord() {
55     BlockCacheTraceRecord record;
56     record.block_type = TraceType::kBlockTraceDataBlock;
57     record.block_size = 4096;
58     record.block_key = kBlockKeyPrefix + std::to_string(kCompactionBlockId);
59     record.access_timestamp = env_->NowMicros();
60     record.cf_id = 0;
61     record.cf_name = "test";
62     record.caller = TableReaderCaller::kCompaction;
63     record.level = 6;
64     record.sst_fd_number = kCompactionBlockId;
65     record.is_cache_hit = Boolean::kFalse;
66     record.no_insert = Boolean::kTrue;
67     return record;
68   }
69 
AssertCache(std::shared_ptr<Cache> sim_cache,const MissRatioStats & miss_ratio_stats,uint64_t expected_usage,uint64_t expected_num_accesses,uint64_t expected_num_misses,std::vector<std::string> blocks,std::vector<std::string> keys)70   void AssertCache(std::shared_ptr<Cache> sim_cache,
71                    const MissRatioStats& miss_ratio_stats,
72                    uint64_t expected_usage, uint64_t expected_num_accesses,
73                    uint64_t expected_num_misses,
74                    std::vector<std::string> blocks,
75                    std::vector<std::string> keys) {
76     EXPECT_EQ(expected_usage, sim_cache->GetUsage());
77     EXPECT_EQ(expected_num_accesses, miss_ratio_stats.total_accesses());
78     EXPECT_EQ(expected_num_misses, miss_ratio_stats.total_misses());
79     for (auto const& block : blocks) {
80       auto handle = sim_cache->Lookup(block);
81       EXPECT_NE(nullptr, handle);
82       sim_cache->Release(handle);
83     }
84     for (auto const& key : keys) {
85       std::string row_key = kRefKeyPrefix + key + kRefKeySequenceNumber;
86       auto handle =
87           sim_cache->Lookup("0_" + ExtractUserKey(row_key).ToString());
88       EXPECT_NE(nullptr, handle);
89       sim_cache->Release(handle);
90     }
91   }
92 
93   Env* env_;
94 };
95 
TEST_F(CacheSimulatorTest,GhostCache)96 TEST_F(CacheSimulatorTest, GhostCache) {
97   const std::string key1 = "test1";
98   const std::string key2 = "test2";
99   std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
100       NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
101                   /*strict_capacity_limit=*/false,
102                   /*high_pri_pool_ratio=*/0)));
103   EXPECT_FALSE(ghost_cache->Admit(key1));
104   EXPECT_TRUE(ghost_cache->Admit(key1));
105   EXPECT_TRUE(ghost_cache->Admit(key1));
106   EXPECT_FALSE(ghost_cache->Admit(key2));
107   EXPECT_TRUE(ghost_cache->Admit(key2));
108 }
109 
TEST_F(CacheSimulatorTest,CacheSimulator)110 TEST_F(CacheSimulatorTest, CacheSimulator) {
111   const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
112   const BlockCacheTraceRecord& compaction_access = GenerateCompactionRecord();
113   std::shared_ptr<Cache> sim_cache =
114       NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
115                   /*strict_capacity_limit=*/false,
116                   /*high_pri_pool_ratio=*/0);
117   std::unique_ptr<CacheSimulator> cache_simulator(
118       new CacheSimulator(nullptr, sim_cache));
119   cache_simulator->Access(access);
120   cache_simulator->Access(access);
121   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
122   ASSERT_EQ(50, cache_simulator->miss_ratio_stats().miss_ratio());
123   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
124   ASSERT_EQ(50, cache_simulator->miss_ratio_stats().user_miss_ratio());
125 
126   cache_simulator->Access(compaction_access);
127   cache_simulator->Access(compaction_access);
128   ASSERT_EQ(4, cache_simulator->miss_ratio_stats().total_accesses());
129   ASSERT_EQ(75, cache_simulator->miss_ratio_stats().miss_ratio());
130   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
131   ASSERT_EQ(50, cache_simulator->miss_ratio_stats().user_miss_ratio());
132 
133   cache_simulator->reset_counter();
134   ASSERT_EQ(0, cache_simulator->miss_ratio_stats().total_accesses());
135   ASSERT_EQ(-1, cache_simulator->miss_ratio_stats().miss_ratio());
136   auto handle = sim_cache->Lookup(access.block_key);
137   ASSERT_NE(nullptr, handle);
138   sim_cache->Release(handle);
139   handle = sim_cache->Lookup(compaction_access.block_key);
140   ASSERT_EQ(nullptr, handle);
141 }
142 
TEST_F(CacheSimulatorTest,GhostCacheSimulator)143 TEST_F(CacheSimulatorTest, GhostCacheSimulator) {
144   const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
145   std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
146       NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
147                   /*strict_capacity_limit=*/false,
148                   /*high_pri_pool_ratio=*/0)));
149   std::unique_ptr<CacheSimulator> cache_simulator(new CacheSimulator(
150       std::move(ghost_cache),
151       NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
152                   /*strict_capacity_limit=*/false,
153                   /*high_pri_pool_ratio=*/0)));
154   cache_simulator->Access(access);
155   cache_simulator->Access(access);
156   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
157   // Both of them will be miss since we have a ghost cache.
158   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
159 }
160 
TEST_F(CacheSimulatorTest,PrioritizedCacheSimulator)161 TEST_F(CacheSimulatorTest, PrioritizedCacheSimulator) {
162   const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
163   std::shared_ptr<Cache> sim_cache =
164       NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
165                   /*strict_capacity_limit=*/false,
166                   /*high_pri_pool_ratio=*/0);
167   std::unique_ptr<PrioritizedCacheSimulator> cache_simulator(
168       new PrioritizedCacheSimulator(nullptr, sim_cache));
169   cache_simulator->Access(access);
170   cache_simulator->Access(access);
171   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
172   ASSERT_EQ(50, cache_simulator->miss_ratio_stats().miss_ratio());
173 
174   auto handle = sim_cache->Lookup(access.block_key);
175   ASSERT_NE(nullptr, handle);
176   sim_cache->Release(handle);
177 }
178 
TEST_F(CacheSimulatorTest,GhostPrioritizedCacheSimulator)179 TEST_F(CacheSimulatorTest, GhostPrioritizedCacheSimulator) {
180   const BlockCacheTraceRecord& access = GenerateGetRecord(kGetId);
181   std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
182       NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
183                   /*strict_capacity_limit=*/false,
184                   /*high_pri_pool_ratio=*/0)));
185   std::unique_ptr<PrioritizedCacheSimulator> cache_simulator(
186       new PrioritizedCacheSimulator(
187           std::move(ghost_cache),
188           NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
189                       /*strict_capacity_limit=*/false,
190                       /*high_pri_pool_ratio=*/0)));
191   cache_simulator->Access(access);
192   cache_simulator->Access(access);
193   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
194   // Both of them will be miss since we have a ghost cache.
195   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
196 }
197 
TEST_F(CacheSimulatorTest,HybridRowBlockCacheSimulator)198 TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulator) {
199   uint64_t block_id = 100;
200   BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId);
201   first_get.get_from_user_specified_snapshot = Boolean::kTrue;
202   BlockCacheTraceRecord second_get = GenerateGetRecord(kGetId + 1);
203   second_get.referenced_data_size = 0;
204   second_get.referenced_key_exist_in_block = Boolean::kFalse;
205   second_get.get_from_user_specified_snapshot = Boolean::kTrue;
206   BlockCacheTraceRecord third_get = GenerateGetRecord(kGetId + 2);
207   third_get.referenced_data_size = 0;
208   third_get.referenced_key_exist_in_block = Boolean::kFalse;
209   third_get.referenced_key = kRefKeyPrefix + "third_get";
210   // We didn't find the referenced key in the third get.
211   third_get.referenced_key_exist_in_block = Boolean::kFalse;
212   third_get.referenced_data_size = 0;
213   std::shared_ptr<Cache> sim_cache =
214       NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
215                   /*strict_capacity_limit=*/false,
216                   /*high_pri_pool_ratio=*/0);
217   std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
218       new HybridRowBlockCacheSimulator(
219           nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/true));
220   // The first get request accesses 10 blocks. We should only report 10 accesses
221   // and 100% miss.
222   for (uint32_t i = 0; i < 10; i++) {
223     first_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
224     cache_simulator->Access(first_get);
225     block_id++;
226   }
227 
228   ASSERT_EQ(10, cache_simulator->miss_ratio_stats().total_accesses());
229   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
230   ASSERT_EQ(10, cache_simulator->miss_ratio_stats().user_accesses());
231   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().user_miss_ratio());
232   auto handle =
233       sim_cache->Lookup(std::to_string(first_get.sst_fd_number) + "_" +
234                         ExtractUserKey(first_get.referenced_key).ToString());
235   ASSERT_NE(nullptr, handle);
236   sim_cache->Release(handle);
237   for (uint32_t i = 100; i < block_id; i++) {
238     handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
239     ASSERT_NE(nullptr, handle);
240     sim_cache->Release(handle);
241   }
242 
243   // The second get request accesses the same key. We should report 15
244   // access and 66% miss, 10 misses with 15 accesses.
245   // We do not consider these 5 block lookups as misses since the row hits the
246   // cache.
247   for (uint32_t i = 0; i < 5; i++) {
248     second_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
249     cache_simulator->Access(second_get);
250     block_id++;
251   }
252   ASSERT_EQ(15, cache_simulator->miss_ratio_stats().total_accesses());
253   ASSERT_EQ(66, static_cast<uint64_t>(
254                     cache_simulator->miss_ratio_stats().miss_ratio()));
255   ASSERT_EQ(15, cache_simulator->miss_ratio_stats().user_accesses());
256   ASSERT_EQ(66, static_cast<uint64_t>(
257                     cache_simulator->miss_ratio_stats().user_miss_ratio()));
258   handle =
259       sim_cache->Lookup(std::to_string(second_get.sst_fd_number) + "_" +
260                         ExtractUserKey(second_get.referenced_key).ToString());
261   ASSERT_NE(nullptr, handle);
262   sim_cache->Release(handle);
263   for (uint32_t i = 100; i < block_id; i++) {
264     handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
265     if (i < 110) {
266       ASSERT_NE(nullptr, handle) << i;
267       sim_cache->Release(handle);
268     } else {
269       ASSERT_EQ(nullptr, handle) << i;
270     }
271   }
272 
273   // The third get on a different key and does not have a size.
274   // This key should not be inserted into the cache.
275   for (uint32_t i = 0; i < 5; i++) {
276     third_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
277     cache_simulator->Access(third_get);
278     block_id++;
279   }
280   ASSERT_EQ(20, cache_simulator->miss_ratio_stats().total_accesses());
281   ASSERT_EQ(75, static_cast<uint64_t>(
282                     cache_simulator->miss_ratio_stats().miss_ratio()));
283   ASSERT_EQ(20, cache_simulator->miss_ratio_stats().user_accesses());
284   ASSERT_EQ(75, static_cast<uint64_t>(
285                     cache_simulator->miss_ratio_stats().user_miss_ratio()));
286   // Assert that the third key is not inserted into the cache.
287   handle = sim_cache->Lookup(std::to_string(third_get.sst_fd_number) + "_" +
288                              third_get.referenced_key);
289   ASSERT_EQ(nullptr, handle);
290   for (uint32_t i = 100; i < block_id; i++) {
291     if (i < 110 || i >= 115) {
292       handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
293       ASSERT_NE(nullptr, handle) << i;
294       sim_cache->Release(handle);
295     } else {
296       handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
297       ASSERT_EQ(nullptr, handle) << i;
298     }
299   }
300 }
301 
TEST_F(CacheSimulatorTest,HybridRowBlockCacheSimulatorGetTest)302 TEST_F(CacheSimulatorTest, HybridRowBlockCacheSimulatorGetTest) {
303   BlockCacheTraceRecord get = GenerateGetRecord(kGetId);
304   get.block_size = 1;
305   get.referenced_data_size = 0;
306   get.access_timestamp = 0;
307   get.block_key = "1";
308   get.get_id = 1;
309   get.get_from_user_specified_snapshot = Boolean::kFalse;
310   get.referenced_key =
311       kRefKeyPrefix + std::to_string(1) + kRefKeySequenceNumber;
312   get.no_insert = Boolean::kFalse;
313   get.sst_fd_number = 0;
314   get.get_from_user_specified_snapshot = Boolean::kFalse;
315 
316   LRUCacheOptions co;
317   co.capacity = 16;
318   co.num_shard_bits = 1;
319   co.strict_capacity_limit = false;
320   co.high_pri_pool_ratio = 0;
321   co.metadata_charge_policy = kDontChargeCacheMetadata;
322   std::shared_ptr<Cache> sim_cache = NewLRUCache(co);
323   std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
324       new HybridRowBlockCacheSimulator(
325           nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/true));
326   // Expect a miss and does not insert the row key-value pair since it does not
327   // have size.
328   cache_simulator->Access(get);
329   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 1, 1, 1, {"1"},
330               {});
331   get.access_timestamp += 1;
332   get.referenced_data_size = 1;
333   get.block_key = "2";
334   cache_simulator->Access(get);
335   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 3, 2, 2,
336               {"1", "2"}, {"1"});
337   get.access_timestamp += 1;
338   get.block_key = "3";
339   // K1 should not inserted again.
340   cache_simulator->Access(get);
341   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 4, 3, 3,
342               {"1", "2", "3"}, {"1"});
343 
344   // A second get request referencing the same key.
345   get.access_timestamp += 1;
346   get.get_id = 2;
347   get.block_key = "4";
348   get.referenced_data_size = 0;
349   cache_simulator->Access(get);
350   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 4, 4, 3,
351               {"1", "2", "3"}, {"1"});
352 
353   // A third get request searches three files, three different keys.
354   // And the second key observes a hit.
355   get.access_timestamp += 1;
356   get.referenced_data_size = 1;
357   get.get_id = 3;
358   get.block_key = "3";
359   get.referenced_key = kRefKeyPrefix + "2" + kRefKeySequenceNumber;
360   // K2 should observe a miss. Block 3 observes a hit.
361   cache_simulator->Access(get);
362   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 5, 3,
363               {"1", "2", "3"}, {"1", "2"});
364 
365   get.access_timestamp += 1;
366   get.referenced_data_size = 1;
367   get.get_id = 3;
368   get.block_key = "4";
369   get.referenced_data_size = 1;
370   get.referenced_key = kRefKeyPrefix + "1" + kRefKeySequenceNumber;
371   // K1 should observe a hit.
372   cache_simulator->Access(get);
373   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 6, 3,
374               {"1", "2", "3"}, {"1", "2"});
375 
376   get.access_timestamp += 1;
377   get.referenced_data_size = 1;
378   get.get_id = 3;
379   get.block_key = "4";
380   get.referenced_data_size = 1;
381   get.referenced_key = kRefKeyPrefix + "3" + kRefKeySequenceNumber;
382   // K3 should observe a miss.
383   // However, as the get already complete, we should not access k3 any more.
384   cache_simulator->Access(get);
385   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 5, 7, 3,
386               {"1", "2", "3"}, {"1", "2"});
387 
388   // A fourth get request searches one file and two blocks. One row key.
389   get.access_timestamp += 1;
390   get.get_id = 4;
391   get.block_key = "5";
392   get.referenced_key = kRefKeyPrefix + "4" + kRefKeySequenceNumber;
393   get.referenced_data_size = 1;
394   cache_simulator->Access(get);
395   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 7, 8, 4,
396               {"1", "2", "3", "5"}, {"1", "2", "4"});
397   for (auto const& key : {"1", "2", "4"}) {
398     auto handle = sim_cache->Lookup("0_" + kRefKeyPrefix + key);
399     ASSERT_NE(nullptr, handle);
400     sim_cache->Release(handle);
401   }
402 
403   // A bunch of insertions which evict cached row keys.
404   for (uint32_t i = 6; i < 100; i++) {
405     get.access_timestamp += 1;
406     get.get_id = 0;
407     get.block_key = std::to_string(i);
408     cache_simulator->Access(get);
409   }
410 
411   get.get_id = 4;
412   // A different block.
413   get.block_key = "100";
414   // Same row key and should not be inserted again.
415   get.referenced_key = kRefKeyPrefix + "4" + kRefKeySequenceNumber;
416   get.referenced_data_size = 1;
417   cache_simulator->Access(get);
418   AssertCache(sim_cache, cache_simulator->miss_ratio_stats(), 16, 103, 99, {},
419               {});
420   for (auto const& key : {"1", "2", "4"}) {
421     auto handle = sim_cache->Lookup("0_" + kRefKeyPrefix + key);
422     ASSERT_EQ(nullptr, handle);
423   }
424 }
425 
TEST_F(CacheSimulatorTest,HybridRowBlockNoInsertCacheSimulator)426 TEST_F(CacheSimulatorTest, HybridRowBlockNoInsertCacheSimulator) {
427   uint64_t block_id = 100;
428   BlockCacheTraceRecord first_get = GenerateGetRecord(kGetId);
429   std::shared_ptr<Cache> sim_cache =
430       NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
431                   /*strict_capacity_limit=*/false,
432                   /*high_pri_pool_ratio=*/0);
433   std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
434       new HybridRowBlockCacheSimulator(
435           nullptr, sim_cache, /*insert_blocks_row_kvpair_misses=*/false));
436   for (uint32_t i = 0; i < 9; i++) {
437     first_get.block_key = kBlockKeyPrefix + std::to_string(block_id);
438     cache_simulator->Access(first_get);
439     block_id++;
440   }
441   auto handle =
442       sim_cache->Lookup(std::to_string(first_get.sst_fd_number) + "_" +
443                         ExtractUserKey(first_get.referenced_key).ToString());
444   ASSERT_NE(nullptr, handle);
445   sim_cache->Release(handle);
446   // All blocks are missing from the cache since insert_blocks_row_kvpair_misses
447   // is set to false.
448   for (uint32_t i = 100; i < block_id; i++) {
449     handle = sim_cache->Lookup(kBlockKeyPrefix + std::to_string(i));
450     ASSERT_EQ(nullptr, handle);
451   }
452 }
453 
TEST_F(CacheSimulatorTest,GhostHybridRowBlockCacheSimulator)454 TEST_F(CacheSimulatorTest, GhostHybridRowBlockCacheSimulator) {
455   std::unique_ptr<GhostCache> ghost_cache(new GhostCache(
456       NewLRUCache(/*capacity=*/kGhostCacheSize, /*num_shard_bits=*/1,
457                   /*strict_capacity_limit=*/false,
458                   /*high_pri_pool_ratio=*/0)));
459   const BlockCacheTraceRecord& first_get = GenerateGetRecord(kGetId);
460   const BlockCacheTraceRecord& second_get = GenerateGetRecord(kGetId + 1);
461   const BlockCacheTraceRecord& third_get = GenerateGetRecord(kGetId + 2);
462   std::unique_ptr<HybridRowBlockCacheSimulator> cache_simulator(
463       new HybridRowBlockCacheSimulator(
464           std::move(ghost_cache),
465           NewLRUCache(/*capacity=*/kCacheSize, /*num_shard_bits=*/1,
466                       /*strict_capacity_limit=*/false,
467                       /*high_pri_pool_ratio=*/0),
468           /*insert_blocks_row_kvpair_misses=*/false));
469   // Two get requests access the same key.
470   cache_simulator->Access(first_get);
471   cache_simulator->Access(second_get);
472   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().total_accesses());
473   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().miss_ratio());
474   ASSERT_EQ(2, cache_simulator->miss_ratio_stats().user_accesses());
475   ASSERT_EQ(100, cache_simulator->miss_ratio_stats().user_miss_ratio());
476   // We insert the key-value pair upon the second get request. A third get
477   // request should observe a hit.
478   for (uint32_t i = 0; i < 10; i++) {
479     cache_simulator->Access(third_get);
480   }
481   ASSERT_EQ(12, cache_simulator->miss_ratio_stats().total_accesses());
482   ASSERT_EQ(16, static_cast<uint64_t>(
483                     cache_simulator->miss_ratio_stats().miss_ratio()));
484   ASSERT_EQ(12, cache_simulator->miss_ratio_stats().user_accesses());
485   ASSERT_EQ(16, static_cast<uint64_t>(
486                     cache_simulator->miss_ratio_stats().user_miss_ratio()));
487 }
488 
489 }  // namespace ROCKSDB_NAMESPACE
490 
main(int argc,char ** argv)491 int main(int argc, char** argv) {
492   ::testing::InitGoogleTest(&argc, argv);
493   return RUN_ALL_TESTS();
494 }
495