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 "db/blob/blob_index.h"
7 #include "db/db_impl/db_impl.h"
8 #include "db/db_test_util.h"
9 #include "db/dbformat.h"
10 #include "db/version_set.h"
11 #include "db/write_batch_internal.h"
12 #include "file/filename.h"
13 #include "logging/logging.h"
14 #include "memtable/hash_linklist_rep.h"
15 #include "monitoring/statistics.h"
16 #include "rocksdb/cache.h"
17 #include "rocksdb/compaction_filter.h"
18 #include "rocksdb/db.h"
19 #include "rocksdb/env.h"
20 #include "rocksdb/filter_policy.h"
21 #include "rocksdb/options.h"
22 #include "rocksdb/perf_context.h"
23 #include "rocksdb/slice.h"
24 #include "rocksdb/slice_transform.h"
25 #include "rocksdb/table.h"
26 #include "rocksdb/table_properties.h"
27 #include "table/block_based/block_based_table_factory.h"
28 #include "table/plain/plain_table_factory.h"
29 #include "test_util/sync_point.h"
30 #include "test_util/testharness.h"
31 #include "test_util/testutil.h"
32 #include "util/hash.h"
33 #include "util/mutexlock.h"
34 #include "util/rate_limiter.h"
35 #include "util/string_util.h"
36 #include "utilities/merge_operators.h"
37 
38 #ifndef ROCKSDB_LITE
39 
40 namespace ROCKSDB_NAMESPACE {
41 
42 class EventListenerTest : public DBTestBase {
43  public:
EventListenerTest()44   EventListenerTest() : DBTestBase("/listener_test") {}
45 
BlobStr(uint64_t blob_file_number,uint64_t offset,uint64_t size)46   static std::string BlobStr(uint64_t blob_file_number, uint64_t offset,
47                              uint64_t size) {
48     std::string blob_index;
49     BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size,
50                           kNoCompression);
51     return blob_index;
52   }
53 
54   const size_t k110KB = 110 << 10;
55 };
56 
57 struct TestPropertiesCollector
58     : public ROCKSDB_NAMESPACE::TablePropertiesCollector {
AddUserKeyROCKSDB_NAMESPACE::TestPropertiesCollector59   ROCKSDB_NAMESPACE::Status AddUserKey(
60       const ROCKSDB_NAMESPACE::Slice& /*key*/,
61       const ROCKSDB_NAMESPACE::Slice& /*value*/,
62       ROCKSDB_NAMESPACE::EntryType /*type*/,
63       ROCKSDB_NAMESPACE::SequenceNumber /*seq*/,
64       uint64_t /*file_size*/) override {
65     return Status::OK();
66   }
FinishROCKSDB_NAMESPACE::TestPropertiesCollector67   ROCKSDB_NAMESPACE::Status Finish(
68       ROCKSDB_NAMESPACE::UserCollectedProperties* properties) override {
69     properties->insert({"0", "1"});
70     return Status::OK();
71   }
72 
NameROCKSDB_NAMESPACE::TestPropertiesCollector73   const char* Name() const override { return "TestTablePropertiesCollector"; }
74 
GetReadablePropertiesROCKSDB_NAMESPACE::TestPropertiesCollector75   ROCKSDB_NAMESPACE::UserCollectedProperties GetReadableProperties()
76       const override {
77     ROCKSDB_NAMESPACE::UserCollectedProperties ret;
78     ret["2"] = "3";
79     return ret;
80   }
81 };
82 
83 class TestPropertiesCollectorFactory : public TablePropertiesCollectorFactory {
84  public:
CreateTablePropertiesCollector(TablePropertiesCollectorFactory::Context)85   TablePropertiesCollector* CreateTablePropertiesCollector(
86       TablePropertiesCollectorFactory::Context /*context*/) override {
87     return new TestPropertiesCollector;
88   }
Name() const89   const char* Name() const override { return "TestTablePropertiesCollector"; }
90 };
91 
92 class TestCompactionListener : public EventListener {
93  public:
TestCompactionListener(EventListenerTest * test)94   explicit TestCompactionListener(EventListenerTest* test) : test_(test) {}
95 
OnCompactionCompleted(DB * db,const CompactionJobInfo & ci)96   void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) override {
97     std::lock_guard<std::mutex> lock(mutex_);
98     compacted_dbs_.push_back(db);
99     ASSERT_GT(ci.input_files.size(), 0U);
100     ASSERT_EQ(ci.input_files.size(), ci.input_file_infos.size());
101 
102     for (size_t i = 0; i < ci.input_file_infos.size(); ++i) {
103       ASSERT_EQ(ci.input_file_infos[i].level, ci.base_input_level);
104       ASSERT_EQ(ci.input_file_infos[i].file_number,
105                 TableFileNameToNumber(ci.input_files[i]));
106     }
107 
108     ASSERT_GT(ci.output_files.size(), 0U);
109     ASSERT_EQ(ci.output_files.size(), ci.output_file_infos.size());
110 
111     ASSERT_TRUE(test_);
112     ASSERT_EQ(test_->db_, db);
113 
114     std::vector<std::vector<FileMetaData>> files_by_level;
115     test_->dbfull()->TEST_GetFilesMetaData(test_->handles_[ci.cf_id],
116                                            &files_by_level);
117     ASSERT_GT(files_by_level.size(), ci.output_level);
118 
119     for (size_t i = 0; i < ci.output_file_infos.size(); ++i) {
120       ASSERT_EQ(ci.output_file_infos[i].level, ci.output_level);
121       ASSERT_EQ(ci.output_file_infos[i].file_number,
122                 TableFileNameToNumber(ci.output_files[i]));
123 
124       auto it = std::find_if(
125           files_by_level[ci.output_level].begin(),
126           files_by_level[ci.output_level].end(), [&](const FileMetaData& meta) {
127             return meta.fd.GetNumber() == ci.output_file_infos[i].file_number;
128           });
129       ASSERT_NE(it, files_by_level[ci.output_level].end());
130 
131       ASSERT_EQ(ci.output_file_infos[i].oldest_blob_file_number,
132                 it->oldest_blob_file_number);
133     }
134 
135     ASSERT_EQ(db->GetEnv()->GetThreadID(), ci.thread_id);
136     ASSERT_GT(ci.thread_id, 0U);
137 
138     for (auto fl : {ci.input_files, ci.output_files}) {
139       for (auto fn : fl) {
140         auto it = ci.table_properties.find(fn);
141         ASSERT_NE(it, ci.table_properties.end());
142         auto tp = it->second;
143         ASSERT_TRUE(tp != nullptr);
144         ASSERT_EQ(tp->user_collected_properties.find("0")->second, "1");
145       }
146     }
147   }
148 
149   EventListenerTest* test_;
150   std::vector<DB*> compacted_dbs_;
151   std::mutex mutex_;
152 };
153 
TEST_F(EventListenerTest,OnSingleDBCompactionTest)154 TEST_F(EventListenerTest, OnSingleDBCompactionTest) {
155   const int kTestKeySize = 16;
156   const int kTestValueSize = 984;
157   const int kEntrySize = kTestKeySize + kTestValueSize;
158   const int kEntriesPerBuffer = 100;
159   const int kNumL0Files = 4;
160 
161   Options options;
162   options.env = CurrentOptions().env;
163   options.create_if_missing = true;
164   options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
165   options.compaction_style = kCompactionStyleLevel;
166   options.target_file_size_base = options.write_buffer_size;
167   options.max_bytes_for_level_base = options.target_file_size_base * 2;
168   options.max_bytes_for_level_multiplier = 2;
169   options.compression = kNoCompression;
170 #ifdef ROCKSDB_USING_THREAD_STATUS
171   options.enable_thread_tracking = true;
172 #endif  // ROCKSDB_USING_THREAD_STATUS
173   options.level0_file_num_compaction_trigger = kNumL0Files;
174   options.table_properties_collector_factories.push_back(
175       std::make_shared<TestPropertiesCollectorFactory>());
176 
177   TestCompactionListener* listener = new TestCompactionListener(this);
178   options.listeners.emplace_back(listener);
179   std::vector<std::string> cf_names = {
180       "pikachu", "ilya", "muromec", "dobrynia",
181       "nikitich", "alyosha", "popovich"};
182   CreateAndReopenWithCF(cf_names, options);
183   ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
184 
185   WriteBatch batch;
186   ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 1, "ditto",
187                                              BlobStr(123, 0, 1 << 10)));
188   ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
189 
190   ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
191   ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
192   ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd')));
193   ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
194   ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
195   ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
196   for (int i = 1; i < 8; ++i) {
197     ASSERT_OK(Flush(i));
198     dbfull()->TEST_WaitForFlushMemTable();
199     ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[i],
200                                      nullptr, nullptr));
201     dbfull()->TEST_WaitForCompact();
202   }
203 
204   ASSERT_EQ(listener->compacted_dbs_.size(), cf_names.size());
205   for (size_t i = 0; i < cf_names.size(); ++i) {
206     ASSERT_EQ(listener->compacted_dbs_[i], db_);
207   }
208 }
209 
210 // This simple Listener can only handle one flush at a time.
211 class TestFlushListener : public EventListener {
212  public:
TestFlushListener(Env * env,EventListenerTest * test)213   TestFlushListener(Env* env, EventListenerTest* test)
214       : slowdown_count(0), stop_count(0), db_closed(), env_(env), test_(test) {
215     db_closed = false;
216   }
OnTableFileCreated(const TableFileCreationInfo & info)217   void OnTableFileCreated(
218       const TableFileCreationInfo& info) override {
219     // remember the info for later checking the FlushJobInfo.
220     prev_fc_info_ = info;
221     ASSERT_GT(info.db_name.size(), 0U);
222     ASSERT_GT(info.cf_name.size(), 0U);
223     ASSERT_GT(info.file_path.size(), 0U);
224     ASSERT_GT(info.job_id, 0);
225     ASSERT_GT(info.table_properties.data_size, 0U);
226     ASSERT_GT(info.table_properties.raw_key_size, 0U);
227     ASSERT_GT(info.table_properties.raw_value_size, 0U);
228     ASSERT_GT(info.table_properties.num_data_blocks, 0U);
229     ASSERT_GT(info.table_properties.num_entries, 0U);
230 
231 #ifdef ROCKSDB_USING_THREAD_STATUS
232     // Verify the id of the current thread that created this table
233     // file matches the id of any active flush or compaction thread.
234     uint64_t thread_id = env_->GetThreadID();
235     std::vector<ThreadStatus> thread_list;
236     ASSERT_OK(env_->GetThreadList(&thread_list));
237     bool found_match = false;
238     for (auto thread_status : thread_list) {
239       if (thread_status.operation_type == ThreadStatus::OP_FLUSH ||
240           thread_status.operation_type == ThreadStatus::OP_COMPACTION) {
241         if (thread_id == thread_status.thread_id) {
242           found_match = true;
243           break;
244         }
245       }
246     }
247     ASSERT_TRUE(found_match);
248 #endif  // ROCKSDB_USING_THREAD_STATUS
249   }
250 
OnFlushCompleted(DB * db,const FlushJobInfo & info)251   void OnFlushCompleted(
252       DB* db, const FlushJobInfo& info) override {
253     flushed_dbs_.push_back(db);
254     flushed_column_family_names_.push_back(info.cf_name);
255     if (info.triggered_writes_slowdown) {
256       slowdown_count++;
257     }
258     if (info.triggered_writes_stop) {
259       stop_count++;
260     }
261     // verify whether the previously created file matches the flushed file.
262     ASSERT_EQ(prev_fc_info_.db_name, db->GetName());
263     ASSERT_EQ(prev_fc_info_.cf_name, info.cf_name);
264     ASSERT_EQ(prev_fc_info_.job_id, info.job_id);
265     ASSERT_EQ(prev_fc_info_.file_path, info.file_path);
266     ASSERT_EQ(TableFileNameToNumber(info.file_path), info.file_number);
267 
268     // Note: the following chunk relies on the notification pertaining to the
269     // database pointed to by DBTestBase::db_, and is thus bypassed when
270     // that assumption does not hold (see the test case MultiDBMultiListeners
271     // below).
272     ASSERT_TRUE(test_);
273     if (db == test_->db_) {
274       std::vector<std::vector<FileMetaData>> files_by_level;
275       test_->dbfull()->TEST_GetFilesMetaData(test_->handles_[info.cf_id],
276                                              &files_by_level);
277 
278       ASSERT_FALSE(files_by_level.empty());
279       auto it = std::find_if(files_by_level[0].begin(), files_by_level[0].end(),
280                              [&](const FileMetaData& meta) {
281                                return meta.fd.GetNumber() == info.file_number;
282                              });
283       ASSERT_NE(it, files_by_level[0].end());
284       ASSERT_EQ(info.oldest_blob_file_number, it->oldest_blob_file_number);
285     }
286 
287     ASSERT_EQ(db->GetEnv()->GetThreadID(), info.thread_id);
288     ASSERT_GT(info.thread_id, 0U);
289     ASSERT_EQ(info.table_properties.user_collected_properties.find("0")->second,
290               "1");
291   }
292 
293   std::vector<std::string> flushed_column_family_names_;
294   std::vector<DB*> flushed_dbs_;
295   int slowdown_count;
296   int stop_count;
297   bool db_closing;
298   std::atomic_bool db_closed;
299   TableFileCreationInfo prev_fc_info_;
300 
301  protected:
302   Env* env_;
303   EventListenerTest* test_;
304 };
305 
TEST_F(EventListenerTest,OnSingleDBFlushTest)306 TEST_F(EventListenerTest, OnSingleDBFlushTest) {
307   Options options;
308   options.env = CurrentOptions().env;
309   options.write_buffer_size = k110KB;
310 #ifdef ROCKSDB_USING_THREAD_STATUS
311   options.enable_thread_tracking = true;
312 #endif  // ROCKSDB_USING_THREAD_STATUS
313   TestFlushListener* listener = new TestFlushListener(options.env, this);
314   options.listeners.emplace_back(listener);
315   std::vector<std::string> cf_names = {
316       "pikachu", "ilya", "muromec", "dobrynia",
317       "nikitich", "alyosha", "popovich"};
318   options.table_properties_collector_factories.push_back(
319       std::make_shared<TestPropertiesCollectorFactory>());
320   CreateAndReopenWithCF(cf_names, options);
321 
322   ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
323 
324   WriteBatch batch;
325   ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 1, "ditto",
326                                              BlobStr(456, 0, 1 << 10)));
327   ASSERT_OK(dbfull()->Write(WriteOptions(), &batch));
328 
329   ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
330   ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
331   ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd')));
332   ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
333   ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
334   ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
335   for (int i = 1; i < 8; ++i) {
336     ASSERT_OK(Flush(i));
337     dbfull()->TEST_WaitForFlushMemTable();
338     ASSERT_EQ(listener->flushed_dbs_.size(), i);
339     ASSERT_EQ(listener->flushed_column_family_names_.size(), i);
340   }
341 
342   // make sure callback functions are called in the right order
343   for (size_t i = 0; i < cf_names.size(); ++i) {
344     ASSERT_EQ(listener->flushed_dbs_[i], db_);
345     ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]);
346   }
347 }
348 
TEST_F(EventListenerTest,MultiCF)349 TEST_F(EventListenerTest, MultiCF) {
350   Options options;
351   options.env = CurrentOptions().env;
352   options.write_buffer_size = k110KB;
353 #ifdef ROCKSDB_USING_THREAD_STATUS
354   options.enable_thread_tracking = true;
355 #endif  // ROCKSDB_USING_THREAD_STATUS
356   TestFlushListener* listener = new TestFlushListener(options.env, this);
357   options.listeners.emplace_back(listener);
358   options.table_properties_collector_factories.push_back(
359       std::make_shared<TestPropertiesCollectorFactory>());
360   std::vector<std::string> cf_names = {
361       "pikachu", "ilya", "muromec", "dobrynia",
362       "nikitich", "alyosha", "popovich"};
363   CreateAndReopenWithCF(cf_names, options);
364 
365   ASSERT_OK(Put(1, "pikachu", std::string(90000, 'p')));
366   ASSERT_OK(Put(2, "ilya", std::string(90000, 'i')));
367   ASSERT_OK(Put(3, "muromec", std::string(90000, 'm')));
368   ASSERT_OK(Put(4, "dobrynia", std::string(90000, 'd')));
369   ASSERT_OK(Put(5, "nikitich", std::string(90000, 'n')));
370   ASSERT_OK(Put(6, "alyosha", std::string(90000, 'a')));
371   ASSERT_OK(Put(7, "popovich", std::string(90000, 'p')));
372   for (int i = 1; i < 8; ++i) {
373     ASSERT_OK(Flush(i));
374     ASSERT_EQ(listener->flushed_dbs_.size(), i);
375     ASSERT_EQ(listener->flushed_column_family_names_.size(), i);
376   }
377 
378   // make sure callback functions are called in the right order
379   for (size_t i = 0; i < cf_names.size(); i++) {
380     ASSERT_EQ(listener->flushed_dbs_[i], db_);
381     ASSERT_EQ(listener->flushed_column_family_names_[i], cf_names[i]);
382   }
383 }
384 
TEST_F(EventListenerTest,MultiDBMultiListeners)385 TEST_F(EventListenerTest, MultiDBMultiListeners) {
386   Options options;
387   options.env = CurrentOptions().env;
388 #ifdef ROCKSDB_USING_THREAD_STATUS
389   options.enable_thread_tracking = true;
390 #endif  // ROCKSDB_USING_THREAD_STATUS
391   options.table_properties_collector_factories.push_back(
392       std::make_shared<TestPropertiesCollectorFactory>());
393   std::vector<TestFlushListener*> listeners;
394   const int kNumDBs = 5;
395   const int kNumListeners = 10;
396   for (int i = 0; i < kNumListeners; ++i) {
397     listeners.emplace_back(new TestFlushListener(options.env, this));
398   }
399 
400   std::vector<std::string> cf_names = {
401       "pikachu", "ilya", "muromec", "dobrynia",
402       "nikitich", "alyosha", "popovich"};
403 
404   options.create_if_missing = true;
405   for (int i = 0; i < kNumListeners; ++i) {
406     options.listeners.emplace_back(listeners[i]);
407   }
408   DBOptions db_opts(options);
409   ColumnFamilyOptions cf_opts(options);
410 
411   std::vector<DB*> dbs;
412   std::vector<std::vector<ColumnFamilyHandle *>> vec_handles;
413 
414   for (int d = 0; d < kNumDBs; ++d) {
415     ASSERT_OK(DestroyDB(dbname_ + ToString(d), options));
416     DB* db;
417     std::vector<ColumnFamilyHandle*> handles;
418     ASSERT_OK(DB::Open(options, dbname_ + ToString(d), &db));
419     for (size_t c = 0; c < cf_names.size(); ++c) {
420       ColumnFamilyHandle* handle;
421       db->CreateColumnFamily(cf_opts, cf_names[c], &handle);
422       handles.push_back(handle);
423     }
424 
425     vec_handles.push_back(std::move(handles));
426     dbs.push_back(db);
427   }
428 
429   for (int d = 0; d < kNumDBs; ++d) {
430     for (size_t c = 0; c < cf_names.size(); ++c) {
431       ASSERT_OK(dbs[d]->Put(WriteOptions(), vec_handles[d][c],
432                 cf_names[c], cf_names[c]));
433     }
434   }
435 
436   for (size_t c = 0; c < cf_names.size(); ++c) {
437     for (int d = 0; d < kNumDBs; ++d) {
438       ASSERT_OK(dbs[d]->Flush(FlushOptions(), vec_handles[d][c]));
439       reinterpret_cast<DBImpl*>(dbs[d])->TEST_WaitForFlushMemTable();
440     }
441   }
442 
443   for (auto* listener : listeners) {
444     int pos = 0;
445     for (size_t c = 0; c < cf_names.size(); ++c) {
446       for (int d = 0; d < kNumDBs; ++d) {
447         ASSERT_EQ(listener->flushed_dbs_[pos], dbs[d]);
448         ASSERT_EQ(listener->flushed_column_family_names_[pos], cf_names[c]);
449         pos++;
450       }
451     }
452   }
453 
454 
455   for (auto handles : vec_handles) {
456     for (auto h : handles) {
457       delete h;
458     }
459     handles.clear();
460   }
461   vec_handles.clear();
462 
463   for (auto db : dbs) {
464     delete db;
465   }
466 }
467 
TEST_F(EventListenerTest,DisableBGCompaction)468 TEST_F(EventListenerTest, DisableBGCompaction) {
469   Options options;
470   options.env = CurrentOptions().env;
471 #ifdef ROCKSDB_USING_THREAD_STATUS
472   options.enable_thread_tracking = true;
473 #endif  // ROCKSDB_USING_THREAD_STATUS
474   TestFlushListener* listener = new TestFlushListener(options.env, this);
475   const int kCompactionTrigger = 1;
476   const int kSlowdownTrigger = 5;
477   const int kStopTrigger = 100;
478   options.level0_file_num_compaction_trigger = kCompactionTrigger;
479   options.level0_slowdown_writes_trigger = kSlowdownTrigger;
480   options.level0_stop_writes_trigger = kStopTrigger;
481   options.max_write_buffer_number = 10;
482   options.listeners.emplace_back(listener);
483   // BG compaction is disabled.  Number of L0 files will simply keeps
484   // increasing in this test.
485   options.compaction_style = kCompactionStyleNone;
486   options.compression = kNoCompression;
487   options.write_buffer_size = 100000;  // Small write buffer
488   options.table_properties_collector_factories.push_back(
489       std::make_shared<TestPropertiesCollectorFactory>());
490 
491   CreateAndReopenWithCF({"pikachu"}, options);
492   ColumnFamilyMetaData cf_meta;
493   db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
494 
495   // keep writing until writes are forced to stop.
496   for (int i = 0; static_cast<int>(cf_meta.file_count) < kSlowdownTrigger * 10;
497        ++i) {
498     Put(1, ToString(i), std::string(10000, 'x'), WriteOptions());
499     FlushOptions fo;
500     fo.allow_write_stall = true;
501     db_->Flush(fo, handles_[1]);
502     db_->GetColumnFamilyMetaData(handles_[1], &cf_meta);
503   }
504   ASSERT_GE(listener->slowdown_count, kSlowdownTrigger * 9);
505 }
506 
507 class TestCompactionReasonListener : public EventListener {
508  public:
OnCompactionCompleted(DB *,const CompactionJobInfo & ci)509   void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
510     std::lock_guard<std::mutex> lock(mutex_);
511     compaction_reasons_.push_back(ci.compaction_reason);
512   }
513 
514   std::vector<CompactionReason> compaction_reasons_;
515   std::mutex mutex_;
516 };
517 
TEST_F(EventListenerTest,CompactionReasonLevel)518 TEST_F(EventListenerTest, CompactionReasonLevel) {
519   Options options;
520   options.env = CurrentOptions().env;
521   options.create_if_missing = true;
522   options.memtable_factory.reset(
523       new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
524 
525   TestCompactionReasonListener* listener = new TestCompactionReasonListener();
526   options.listeners.emplace_back(listener);
527 
528   options.level0_file_num_compaction_trigger = 4;
529   options.compaction_style = kCompactionStyleLevel;
530 
531   DestroyAndReopen(options);
532   Random rnd(301);
533 
534   // Write 4 files in L0
535   for (int i = 0; i < 4; i++) {
536     GenerateNewRandomFile(&rnd);
537   }
538   dbfull()->TEST_WaitForCompact();
539 
540   ASSERT_EQ(listener->compaction_reasons_.size(), 1);
541   ASSERT_EQ(listener->compaction_reasons_[0],
542             CompactionReason::kLevelL0FilesNum);
543 
544   DestroyAndReopen(options);
545 
546   // Write 3 non-overlapping files in L0
547   for (int k = 1; k <= 30; k++) {
548     ASSERT_OK(Put(Key(k), Key(k)));
549     if (k % 10 == 0) {
550       Flush();
551     }
552   }
553 
554   // Do a trivial move from L0 -> L1
555   db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
556 
557   options.max_bytes_for_level_base = 1;
558   Close();
559   listener->compaction_reasons_.clear();
560   Reopen(options);
561 
562   dbfull()->TEST_WaitForCompact();
563   ASSERT_GT(listener->compaction_reasons_.size(), 1);
564 
565   for (auto compaction_reason : listener->compaction_reasons_) {
566     ASSERT_EQ(compaction_reason, CompactionReason::kLevelMaxLevelSize);
567   }
568 
569   options.disable_auto_compactions = true;
570   Close();
571   listener->compaction_reasons_.clear();
572   Reopen(options);
573 
574   Put("key", "value");
575   CompactRangeOptions cro;
576   cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized;
577   ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
578   ASSERT_GT(listener->compaction_reasons_.size(), 0);
579   for (auto compaction_reason : listener->compaction_reasons_) {
580     ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
581   }
582 }
583 
TEST_F(EventListenerTest,CompactionReasonUniversal)584 TEST_F(EventListenerTest, CompactionReasonUniversal) {
585   Options options;
586   options.env = CurrentOptions().env;
587   options.create_if_missing = true;
588   options.memtable_factory.reset(
589       new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
590 
591   TestCompactionReasonListener* listener = new TestCompactionReasonListener();
592   options.listeners.emplace_back(listener);
593 
594   options.compaction_style = kCompactionStyleUniversal;
595 
596   Random rnd(301);
597 
598   options.level0_file_num_compaction_trigger = 8;
599   options.compaction_options_universal.max_size_amplification_percent = 100000;
600   options.compaction_options_universal.size_ratio = 100000;
601   DestroyAndReopen(options);
602   listener->compaction_reasons_.clear();
603 
604   // Write 8 files in L0
605   for (int i = 0; i < 8; i++) {
606     GenerateNewRandomFile(&rnd);
607   }
608   dbfull()->TEST_WaitForCompact();
609 
610   ASSERT_GT(listener->compaction_reasons_.size(), 0);
611   for (auto compaction_reason : listener->compaction_reasons_) {
612     ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeRatio);
613   }
614 
615   options.level0_file_num_compaction_trigger = 8;
616   options.compaction_options_universal.max_size_amplification_percent = 1;
617   options.compaction_options_universal.size_ratio = 100000;
618 
619   DestroyAndReopen(options);
620   listener->compaction_reasons_.clear();
621 
622   // Write 8 files in L0
623   for (int i = 0; i < 8; i++) {
624     GenerateNewRandomFile(&rnd);
625   }
626   dbfull()->TEST_WaitForCompact();
627 
628   ASSERT_GT(listener->compaction_reasons_.size(), 0);
629   for (auto compaction_reason : listener->compaction_reasons_) {
630     ASSERT_EQ(compaction_reason, CompactionReason::kUniversalSizeAmplification);
631   }
632 
633   options.disable_auto_compactions = true;
634   Close();
635   listener->compaction_reasons_.clear();
636   Reopen(options);
637 
638   db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
639 
640   ASSERT_GT(listener->compaction_reasons_.size(), 0);
641   for (auto compaction_reason : listener->compaction_reasons_) {
642     ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
643   }
644 }
645 
TEST_F(EventListenerTest,CompactionReasonFIFO)646 TEST_F(EventListenerTest, CompactionReasonFIFO) {
647   Options options;
648   options.env = CurrentOptions().env;
649   options.create_if_missing = true;
650   options.memtable_factory.reset(
651       new SpecialSkipListFactory(DBTestBase::kNumKeysByGenerateNewRandomFile));
652 
653   TestCompactionReasonListener* listener = new TestCompactionReasonListener();
654   options.listeners.emplace_back(listener);
655 
656   options.level0_file_num_compaction_trigger = 4;
657   options.compaction_style = kCompactionStyleFIFO;
658   options.compaction_options_fifo.max_table_files_size = 1;
659 
660   DestroyAndReopen(options);
661   Random rnd(301);
662 
663   // Write 4 files in L0
664   for (int i = 0; i < 4; i++) {
665     GenerateNewRandomFile(&rnd);
666   }
667   dbfull()->TEST_WaitForCompact();
668 
669   ASSERT_GT(listener->compaction_reasons_.size(), 0);
670   for (auto compaction_reason : listener->compaction_reasons_) {
671     ASSERT_EQ(compaction_reason, CompactionReason::kFIFOMaxSize);
672   }
673 }
674 
675 class TableFileCreationListener : public EventListener {
676  public:
677   class TestEnv : public EnvWrapper {
678    public:
TestEnv()679     TestEnv() : EnvWrapper(Env::Default()) {}
680 
SetStatus(Status s)681     void SetStatus(Status s) { status_ = s; }
682 
NewWritableFile(const std::string & fname,std::unique_ptr<WritableFile> * result,const EnvOptions & options)683     Status NewWritableFile(const std::string& fname,
684                            std::unique_ptr<WritableFile>* result,
685                            const EnvOptions& options) override {
686       if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") {
687         if (!status_.ok()) {
688           return status_;
689         }
690       }
691       return Env::Default()->NewWritableFile(fname, result, options);
692     }
693 
694    private:
695     Status status_;
696   };
697 
TableFileCreationListener()698   TableFileCreationListener() {
699     for (int i = 0; i < 2; i++) {
700       started_[i] = finished_[i] = failure_[i] = 0;
701     }
702   }
703 
Index(TableFileCreationReason reason)704   int Index(TableFileCreationReason reason) {
705     int idx;
706     switch (reason) {
707       case TableFileCreationReason::kFlush:
708         idx = 0;
709         break;
710       case TableFileCreationReason::kCompaction:
711         idx = 1;
712         break;
713       default:
714         idx = -1;
715     }
716     return idx;
717   }
718 
CheckAndResetCounters(int flush_started,int flush_finished,int flush_failure,int compaction_started,int compaction_finished,int compaction_failure)719   void CheckAndResetCounters(int flush_started, int flush_finished,
720                              int flush_failure, int compaction_started,
721                              int compaction_finished, int compaction_failure) {
722     ASSERT_EQ(started_[0], flush_started);
723     ASSERT_EQ(finished_[0], flush_finished);
724     ASSERT_EQ(failure_[0], flush_failure);
725     ASSERT_EQ(started_[1], compaction_started);
726     ASSERT_EQ(finished_[1], compaction_finished);
727     ASSERT_EQ(failure_[1], compaction_failure);
728     for (int i = 0; i < 2; i++) {
729       started_[i] = finished_[i] = failure_[i] = 0;
730     }
731   }
732 
OnTableFileCreationStarted(const TableFileCreationBriefInfo & info)733   void OnTableFileCreationStarted(
734       const TableFileCreationBriefInfo& info) override {
735     int idx = Index(info.reason);
736     if (idx >= 0) {
737       started_[idx]++;
738     }
739     ASSERT_GT(info.db_name.size(), 0U);
740     ASSERT_GT(info.cf_name.size(), 0U);
741     ASSERT_GT(info.file_path.size(), 0U);
742     ASSERT_GT(info.job_id, 0);
743   }
744 
OnTableFileCreated(const TableFileCreationInfo & info)745   void OnTableFileCreated(const TableFileCreationInfo& info) override {
746     int idx = Index(info.reason);
747     if (idx >= 0) {
748       finished_[idx]++;
749     }
750     ASSERT_GT(info.db_name.size(), 0U);
751     ASSERT_GT(info.cf_name.size(), 0U);
752     ASSERT_GT(info.file_path.size(), 0U);
753     ASSERT_GT(info.job_id, 0);
754     if (info.status.ok()) {
755       ASSERT_GT(info.table_properties.data_size, 0U);
756       ASSERT_GT(info.table_properties.raw_key_size, 0U);
757       ASSERT_GT(info.table_properties.raw_value_size, 0U);
758       ASSERT_GT(info.table_properties.num_data_blocks, 0U);
759       ASSERT_GT(info.table_properties.num_entries, 0U);
760     } else {
761       if (idx >= 0) {
762         failure_[idx]++;
763       }
764     }
765   }
766 
767   TestEnv test_env;
768   int started_[2];
769   int finished_[2];
770   int failure_[2];
771 };
772 
TEST_F(EventListenerTest,TableFileCreationListenersTest)773 TEST_F(EventListenerTest, TableFileCreationListenersTest) {
774   auto listener = std::make_shared<TableFileCreationListener>();
775   Options options;
776   options.create_if_missing = true;
777   options.listeners.push_back(listener);
778   options.env = &listener->test_env;
779   DestroyAndReopen(options);
780 
781   ASSERT_OK(Put("foo", "aaa"));
782   ASSERT_OK(Put("bar", "bbb"));
783   ASSERT_OK(Flush());
784   dbfull()->TEST_WaitForFlushMemTable();
785   listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
786 
787   ASSERT_OK(Put("foo", "aaa1"));
788   ASSERT_OK(Put("bar", "bbb1"));
789   listener->test_env.SetStatus(Status::NotSupported("not supported"));
790   ASSERT_NOK(Flush());
791   listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0);
792   listener->test_env.SetStatus(Status::OK());
793 
794   Reopen(options);
795   ASSERT_OK(Put("foo", "aaa2"));
796   ASSERT_OK(Put("bar", "bbb2"));
797   ASSERT_OK(Flush());
798   dbfull()->TEST_WaitForFlushMemTable();
799   listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
800 
801   const Slice kRangeStart = "a";
802   const Slice kRangeEnd = "z";
803   dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
804   dbfull()->TEST_WaitForCompact();
805   listener->CheckAndResetCounters(0, 0, 0, 1, 1, 0);
806 
807   ASSERT_OK(Put("foo", "aaa3"));
808   ASSERT_OK(Put("bar", "bbb3"));
809   ASSERT_OK(Flush());
810   listener->test_env.SetStatus(Status::NotSupported("not supported"));
811   dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
812   dbfull()->TEST_WaitForCompact();
813   listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
814 }
815 
816 class MemTableSealedListener : public EventListener {
817 private:
818   SequenceNumber latest_seq_number_;
819 public:
MemTableSealedListener()820   MemTableSealedListener() {}
OnMemTableSealed(const MemTableInfo & info)821   void OnMemTableSealed(const MemTableInfo& info) override {
822     latest_seq_number_ = info.first_seqno;
823   }
824 
OnFlushCompleted(DB *,const FlushJobInfo & flush_job_info)825   void OnFlushCompleted(DB* /*db*/,
826     const FlushJobInfo& flush_job_info) override {
827     ASSERT_LE(flush_job_info.smallest_seqno, latest_seq_number_);
828   }
829 };
830 
TEST_F(EventListenerTest,MemTableSealedListenerTest)831 TEST_F(EventListenerTest, MemTableSealedListenerTest) {
832   auto listener = std::make_shared<MemTableSealedListener>();
833   Options options;
834   options.create_if_missing = true;
835   options.listeners.push_back(listener);
836   DestroyAndReopen(options);
837 
838   for (unsigned int i = 0; i < 10; i++) {
839     std::string tag = std::to_string(i);
840     ASSERT_OK(Put("foo"+tag, "aaa"));
841     ASSERT_OK(Put("bar"+tag, "bbb"));
842 
843     ASSERT_OK(Flush());
844   }
845 }
846 
847 class ColumnFamilyHandleDeletionStartedListener : public EventListener {
848  private:
849   std::vector<std::string> cfs_;
850   int counter;
851 
852  public:
ColumnFamilyHandleDeletionStartedListener(const std::vector<std::string> & cfs)853   explicit ColumnFamilyHandleDeletionStartedListener(
854       const std::vector<std::string>& cfs)
855       : cfs_(cfs), counter(0) {
856     cfs_.insert(cfs_.begin(), kDefaultColumnFamilyName);
857   }
OnColumnFamilyHandleDeletionStarted(ColumnFamilyHandle * handle)858   void OnColumnFamilyHandleDeletionStarted(
859       ColumnFamilyHandle* handle) override {
860     ASSERT_EQ(cfs_[handle->GetID()], handle->GetName());
861     counter++;
862   }
getCounter()863   int getCounter() { return counter; }
864 };
865 
TEST_F(EventListenerTest,ColumnFamilyHandleDeletionStartedListenerTest)866 TEST_F(EventListenerTest, ColumnFamilyHandleDeletionStartedListenerTest) {
867   std::vector<std::string> cfs{"pikachu", "eevee", "Mewtwo"};
868   auto listener =
869       std::make_shared<ColumnFamilyHandleDeletionStartedListener>(cfs);
870   Options options;
871   options.env = CurrentOptions().env;
872   options.create_if_missing = true;
873   options.listeners.push_back(listener);
874   CreateAndReopenWithCF(cfs, options);
875   ASSERT_EQ(handles_.size(), 4);
876   delete handles_[3];
877   delete handles_[2];
878   delete handles_[1];
879   handles_.resize(1);
880   ASSERT_EQ(listener->getCounter(), 3);
881 }
882 
883 class BackgroundErrorListener : public EventListener {
884  private:
885   SpecialEnv* env_;
886   int counter_;
887 
888  public:
BackgroundErrorListener(SpecialEnv * env)889   BackgroundErrorListener(SpecialEnv* env) : env_(env), counter_(0) {}
890 
OnBackgroundError(BackgroundErrorReason,Status * bg_error)891   void OnBackgroundError(BackgroundErrorReason /*reason*/,
892                          Status* bg_error) override {
893     if (counter_ == 0) {
894       // suppress the first error and disable write-dropping such that a retry
895       // can succeed.
896       *bg_error = Status::OK();
897       env_->drop_writes_.store(false, std::memory_order_release);
898       env_->no_slowdown_ = false;
899     }
900     ++counter_;
901   }
902 
counter()903   int counter() { return counter_; }
904 };
905 
TEST_F(EventListenerTest,BackgroundErrorListenerFailedFlushTest)906 TEST_F(EventListenerTest, BackgroundErrorListenerFailedFlushTest) {
907   auto listener = std::make_shared<BackgroundErrorListener>(env_);
908   Options options;
909   options.create_if_missing = true;
910   options.env = env_;
911   options.listeners.push_back(listener);
912   options.memtable_factory.reset(new SpecialSkipListFactory(1));
913   options.paranoid_checks = true;
914   DestroyAndReopen(options);
915 
916   // the usual TEST_WaitForFlushMemTable() doesn't work for failed flushes, so
917   // forge a custom one for the failed flush case.
918   ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->LoadDependency(
919       {{"DBImpl::BGWorkFlush:done",
920         "EventListenerTest:BackgroundErrorListenerFailedFlushTest:1"}});
921   ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
922 
923   env_->drop_writes_.store(true, std::memory_order_release);
924   env_->no_slowdown_ = true;
925 
926   ASSERT_OK(Put("key0", "val"));
927   ASSERT_OK(Put("key1", "val"));
928   TEST_SYNC_POINT("EventListenerTest:BackgroundErrorListenerFailedFlushTest:1");
929   ASSERT_EQ(1, listener->counter());
930   ASSERT_OK(Put("key2", "val"));
931   ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
932   ASSERT_EQ(1, NumTableFilesAtLevel(0));
933 }
934 
TEST_F(EventListenerTest,BackgroundErrorListenerFailedCompactionTest)935 TEST_F(EventListenerTest, BackgroundErrorListenerFailedCompactionTest) {
936   auto listener = std::make_shared<BackgroundErrorListener>(env_);
937   Options options;
938   options.create_if_missing = true;
939   options.disable_auto_compactions = true;
940   options.env = env_;
941   options.level0_file_num_compaction_trigger = 2;
942   options.listeners.push_back(listener);
943   options.memtable_factory.reset(new SpecialSkipListFactory(2));
944   options.paranoid_checks = true;
945   DestroyAndReopen(options);
946 
947   // third iteration triggers the second memtable's flush
948   for (int i = 0; i < 3; ++i) {
949     ASSERT_OK(Put("key0", "val"));
950     if (i > 0) {
951       ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
952     }
953     ASSERT_OK(Put("key1", "val"));
954   }
955   ASSERT_EQ(2, NumTableFilesAtLevel(0));
956 
957   env_->drop_writes_.store(true, std::memory_order_release);
958   env_->no_slowdown_ = true;
959   ASSERT_OK(dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
960   ASSERT_OK(dbfull()->TEST_WaitForCompact());
961   ASSERT_EQ(1, listener->counter());
962 
963   // trigger flush so compaction is triggered again; this time it succeeds
964   // The previous failed compaction may get retried automatically, so we may
965   // be left with 0 or 1 files in level 1, depending on when the retry gets
966   // scheduled
967   ASSERT_OK(Put("key0", "val"));
968   ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
969   ASSERT_OK(dbfull()->TEST_WaitForCompact());
970   ASSERT_LE(1, NumTableFilesAtLevel(0));
971 }
972 
973 class TestFileOperationListener : public EventListener {
974  public:
TestFileOperationListener()975   TestFileOperationListener() {
976     file_reads_.store(0);
977     file_reads_success_.store(0);
978     file_writes_.store(0);
979     file_writes_success_.store(0);
980   }
981 
OnFileReadFinish(const FileOperationInfo & info)982   void OnFileReadFinish(const FileOperationInfo& info) override {
983     ++file_reads_;
984     if (info.status.ok()) {
985       ++file_reads_success_;
986     }
987     ReportDuration(info);
988   }
989 
OnFileWriteFinish(const FileOperationInfo & info)990   void OnFileWriteFinish(const FileOperationInfo& info) override {
991     ++file_writes_;
992     if (info.status.ok()) {
993       ++file_writes_success_;
994     }
995     ReportDuration(info);
996   }
997 
ShouldBeNotifiedOnFileIO()998   bool ShouldBeNotifiedOnFileIO() override { return true; }
999 
1000   std::atomic<size_t> file_reads_;
1001   std::atomic<size_t> file_reads_success_;
1002   std::atomic<size_t> file_writes_;
1003   std::atomic<size_t> file_writes_success_;
1004 
1005  private:
ReportDuration(const FileOperationInfo & info) const1006   void ReportDuration(const FileOperationInfo& info) const {
1007     auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(
1008         info.finish_timestamp - info.start_timestamp);
1009     ASSERT_GT(duration.count(), 0);
1010   }
1011 };
1012 
TEST_F(EventListenerTest,OnFileOperationTest)1013 TEST_F(EventListenerTest, OnFileOperationTest) {
1014   Options options;
1015   options.env = CurrentOptions().env;
1016   options.create_if_missing = true;
1017 
1018   TestFileOperationListener* listener = new TestFileOperationListener();
1019   options.listeners.emplace_back(listener);
1020 
1021   DestroyAndReopen(options);
1022   ASSERT_OK(Put("foo", "aaa"));
1023   dbfull()->Flush(FlushOptions());
1024   dbfull()->TEST_WaitForFlushMemTable();
1025   ASSERT_GE(listener->file_writes_.load(),
1026             listener->file_writes_success_.load());
1027   ASSERT_GT(listener->file_writes_.load(), 0);
1028   Close();
1029 
1030   Reopen(options);
1031   ASSERT_GE(listener->file_reads_.load(), listener->file_reads_success_.load());
1032   ASSERT_GT(listener->file_reads_.load(), 0);
1033 }
1034 
1035 }  // namespace ROCKSDB_NAMESPACE
1036 
1037 #endif  // ROCKSDB_LITE
1038 
main(int argc,char ** argv)1039 int main(int argc, char** argv) {
1040   ::testing::InitGoogleTest(&argc, argv);
1041   return RUN_ALL_TESTS();
1042 }
1043