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