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 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9 
10 #ifdef GFLAGS
11 #include "db_stress_tool/db_stress_common.h"
12 
13 namespace ROCKSDB_NAMESPACE {
14 class BatchedOpsStressTest : public StressTest {
15  public:
BatchedOpsStressTest()16   BatchedOpsStressTest() {}
~BatchedOpsStressTest()17   virtual ~BatchedOpsStressTest() {}
18 
19   // Given a key K and value V, this puts ("0"+K, "0"+V), ("1"+K, "1"+V), ...
20   // ("9"+K, "9"+V) in DB atomically i.e in a single batch.
21   // Also refer BatchedOpsStressTest::TestGet
TestPut(ThreadState * thread,WriteOptions & write_opts,const ReadOptions &,const std::vector<int> & rand_column_families,const std::vector<int64_t> & rand_keys,char (& value)[100],std::unique_ptr<MutexLock> &)22   Status TestPut(ThreadState* thread, WriteOptions& write_opts,
23                  const ReadOptions& /* read_opts */,
24                  const std::vector<int>& rand_column_families,
25                  const std::vector<int64_t>& rand_keys, char (&value)[100],
26                  std::unique_ptr<MutexLock>& /* lock */) override {
27     uint32_t value_base =
28         thread->rand.Next() % thread->shared->UNKNOWN_SENTINEL;
29     size_t sz = GenerateValue(value_base, value, sizeof(value));
30     Slice v(value, sz);
31     std::string keys[10] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
32     std::string values[10] = {"9", "8", "7", "6", "5", "4", "3", "2", "1", "0"};
33     Slice value_slices[10];
34     WriteBatch batch;
35     Status s;
36     auto cfh = column_families_[rand_column_families[0]];
37     std::string key_str = Key(rand_keys[0]);
38     for (int i = 0; i < 10; i++) {
39       keys[i] += key_str;
40       values[i] += v.ToString();
41       value_slices[i] = values[i];
42       if (FLAGS_use_merge) {
43         batch.Merge(cfh, keys[i], value_slices[i]);
44       } else {
45         batch.Put(cfh, keys[i], value_slices[i]);
46       }
47     }
48 
49     s = db_->Write(write_opts, &batch);
50     if (!s.ok()) {
51       fprintf(stderr, "multiput error: %s\n", s.ToString().c_str());
52       thread->stats.AddErrors(1);
53     } else {
54       // we did 10 writes each of size sz + 1
55       thread->stats.AddBytesForWrites(10, (sz + 1) * 10);
56     }
57 
58     return s;
59   }
60 
61   // Given a key K, this deletes ("0"+K), ("1"+K),... ("9"+K)
62   // in DB atomically i.e in a single batch. Also refer MultiGet.
TestDelete(ThreadState * thread,WriteOptions & writeoptions,const std::vector<int> & rand_column_families,const std::vector<int64_t> & rand_keys,std::unique_ptr<MutexLock> &)63   Status TestDelete(ThreadState* thread, WriteOptions& writeoptions,
64                     const std::vector<int>& rand_column_families,
65                     const std::vector<int64_t>& rand_keys,
66                     std::unique_ptr<MutexLock>& /* lock */) override {
67     std::string keys[10] = {"9", "7", "5", "3", "1", "8", "6", "4", "2", "0"};
68 
69     WriteBatch batch;
70     Status s;
71     auto cfh = column_families_[rand_column_families[0]];
72     std::string key_str = Key(rand_keys[0]);
73     for (int i = 0; i < 10; i++) {
74       keys[i] += key_str;
75       batch.Delete(cfh, keys[i]);
76     }
77 
78     s = db_->Write(writeoptions, &batch);
79     if (!s.ok()) {
80       fprintf(stderr, "multidelete error: %s\n", s.ToString().c_str());
81       thread->stats.AddErrors(1);
82     } else {
83       thread->stats.AddDeletes(10);
84     }
85 
86     return s;
87   }
88 
TestDeleteRange(ThreadState *,WriteOptions &,const std::vector<int> &,const std::vector<int64_t> &,std::unique_ptr<MutexLock> &)89   Status TestDeleteRange(ThreadState* /* thread */,
90                          WriteOptions& /* write_opts */,
91                          const std::vector<int>& /* rand_column_families */,
92                          const std::vector<int64_t>& /* rand_keys */,
93                          std::unique_ptr<MutexLock>& /* lock */) override {
94     assert(false);
95     return Status::NotSupported(
96         "BatchedOpsStressTest does not support "
97         "TestDeleteRange");
98   }
99 
TestIngestExternalFile(ThreadState *,const std::vector<int> &,const std::vector<int64_t> &,std::unique_ptr<MutexLock> &)100   void TestIngestExternalFile(
101       ThreadState* /* thread */,
102       const std::vector<int>& /* rand_column_families */,
103       const std::vector<int64_t>& /* rand_keys */,
104       std::unique_ptr<MutexLock>& /* lock */) override {
105     assert(false);
106     fprintf(stderr,
107             "BatchedOpsStressTest does not support "
108             "TestIngestExternalFile\n");
109     std::terminate();
110   }
111 
112   // Given a key K, this gets values for "0"+K, "1"+K,..."9"+K
113   // in the same snapshot, and verifies that all the values are of the form
114   // "0"+V, "1"+V,..."9"+V.
115   // ASSUMES that BatchedOpsStressTest::TestPut was used to put (K, V) into
116   // the DB.
TestGet(ThreadState * thread,const ReadOptions & readoptions,const std::vector<int> & rand_column_families,const std::vector<int64_t> & rand_keys)117   Status TestGet(ThreadState* thread, const ReadOptions& readoptions,
118                  const std::vector<int>& rand_column_families,
119                  const std::vector<int64_t>& rand_keys) override {
120     std::string keys[10] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
121     Slice key_slices[10];
122     std::string values[10];
123     ReadOptions readoptionscopy = readoptions;
124     readoptionscopy.snapshot = db_->GetSnapshot();
125     std::string key_str = Key(rand_keys[0]);
126     Slice key = key_str;
127     auto cfh = column_families_[rand_column_families[0]];
128     std::string from_db;
129     Status s;
130     for (int i = 0; i < 10; i++) {
131       keys[i] += key.ToString();
132       key_slices[i] = keys[i];
133       s = db_->Get(readoptionscopy, cfh, key_slices[i], &from_db);
134       if (!s.ok() && !s.IsNotFound()) {
135         fprintf(stderr, "get error: %s\n", s.ToString().c_str());
136         values[i] = "";
137         thread->stats.AddErrors(1);
138         // we continue after error rather than exiting so that we can
139         // find more errors if any
140       } else if (s.IsNotFound()) {
141         values[i] = "";
142         thread->stats.AddGets(1, 0);
143       } else {
144         values[i] = from_db;
145 
146         char expected_prefix = (keys[i])[0];
147         char actual_prefix = (values[i])[0];
148         if (actual_prefix != expected_prefix) {
149           fprintf(stderr, "error expected prefix = %c actual = %c\n",
150                   expected_prefix, actual_prefix);
151         }
152         (values[i])[0] = ' ';  // blank out the differing character
153         thread->stats.AddGets(1, 1);
154       }
155     }
156     db_->ReleaseSnapshot(readoptionscopy.snapshot);
157 
158     // Now that we retrieved all values, check that they all match
159     for (int i = 1; i < 10; i++) {
160       if (values[i] != values[0]) {
161         fprintf(stderr, "error : inconsistent values for key %s: %s, %s\n",
162                 key.ToString(true).c_str(), StringToHex(values[0]).c_str(),
163                 StringToHex(values[i]).c_str());
164         // we continue after error rather than exiting so that we can
165         // find more errors if any
166       }
167     }
168 
169     return s;
170   }
171 
TestMultiGet(ThreadState * thread,const ReadOptions & readoptions,const std::vector<int> & rand_column_families,const std::vector<int64_t> & rand_keys)172   std::vector<Status> TestMultiGet(
173       ThreadState* thread, const ReadOptions& readoptions,
174       const std::vector<int>& rand_column_families,
175       const std::vector<int64_t>& rand_keys) override {
176     size_t num_keys = rand_keys.size();
177     std::vector<Status> ret_status(num_keys);
178     std::array<std::string, 10> keys = {{"0", "1", "2", "3", "4",
179                                          "5", "6", "7", "8", "9"}};
180     size_t num_prefixes = keys.size();
181     for (size_t rand_key = 0; rand_key < num_keys; ++rand_key) {
182       std::vector<Slice> key_slices;
183       std::vector<PinnableSlice> values(num_prefixes);
184       std::vector<Status> statuses(num_prefixes);
185       ReadOptions readoptionscopy = readoptions;
186       readoptionscopy.snapshot = db_->GetSnapshot();
187       std::vector<std::string> key_str;
188       key_str.reserve(num_prefixes);
189       key_slices.reserve(num_prefixes);
190       std::string from_db;
191       ColumnFamilyHandle* cfh = column_families_[rand_column_families[0]];
192 
193       for (size_t key = 0; key < num_prefixes; ++key) {
194         key_str.emplace_back(keys[key] + Key(rand_keys[rand_key]));
195         key_slices.emplace_back(key_str.back());
196       }
197       db_->MultiGet(readoptionscopy, cfh, num_prefixes, key_slices.data(),
198                     values.data(), statuses.data());
199       for (size_t i = 0; i < num_prefixes; i++) {
200         Status s = statuses[i];
201         if (!s.ok() && !s.IsNotFound()) {
202           fprintf(stderr, "get error: %s\n", s.ToString().c_str());
203           thread->stats.AddErrors(1);
204           ret_status[rand_key] = s;
205           // we continue after error rather than exiting so that we can
206           // find more errors if any
207         } else if (s.IsNotFound()) {
208           thread->stats.AddGets(1, 0);
209           ret_status[rand_key] = s;
210         } else {
211           char expected_prefix = (keys[i])[0];
212           char actual_prefix = (values[i])[0];
213           if (actual_prefix != expected_prefix) {
214             fprintf(stderr, "error expected prefix = %c actual = %c\n",
215                     expected_prefix, actual_prefix);
216           }
217           std::string str;
218           str.assign(values[i].data(), values[i].size());
219           values[i].Reset();
220           str[0] = ' ';  // blank out the differing character
221           values[i].PinSelf(str);
222           thread->stats.AddGets(1, 1);
223         }
224       }
225       db_->ReleaseSnapshot(readoptionscopy.snapshot);
226 
227       // Now that we retrieved all values, check that they all match
228       for (size_t i = 1; i < num_prefixes; i++) {
229         if (values[i] != values[0]) {
230           fprintf(stderr, "error : inconsistent values for key %s: %s, %s\n",
231                   key_str[i].c_str(), StringToHex(values[0].ToString()).c_str(),
232                   StringToHex(values[i].ToString()).c_str());
233           // we continue after error rather than exiting so that we can
234           // find more errors if any
235         }
236       }
237     }
238 
239     return ret_status;
240   }
241 
242   // Given a key, this does prefix scans for "0"+P, "1"+P,..."9"+P
243   // in the same snapshot where P is the first FLAGS_prefix_size - 1 bytes
244   // of the key. Each of these 10 scans returns a series of values;
245   // each series should be the same length, and it is verified for each
246   // index i that all the i'th values are of the form "0"+V, "1"+V,..."9"+V.
247   // ASSUMES that MultiPut was used to put (K, V)
TestPrefixScan(ThreadState * thread,const ReadOptions & readoptions,const std::vector<int> & rand_column_families,const std::vector<int64_t> & rand_keys)248   Status TestPrefixScan(ThreadState* thread, const ReadOptions& readoptions,
249                         const std::vector<int>& rand_column_families,
250                         const std::vector<int64_t>& rand_keys) override {
251     size_t prefix_to_use =
252         (FLAGS_prefix_size < 0) ? 7 : static_cast<size_t>(FLAGS_prefix_size);
253     std::string key_str = Key(rand_keys[0]);
254     Slice key = key_str;
255     auto cfh = column_families_[rand_column_families[0]];
256     std::string prefixes[10] = {"0", "1", "2", "3", "4",
257                                 "5", "6", "7", "8", "9"};
258     Slice prefix_slices[10];
259     ReadOptions readoptionscopy[10];
260     const Snapshot* snapshot = db_->GetSnapshot();
261     Iterator* iters[10];
262     std::string upper_bounds[10];
263     Slice ub_slices[10];
264     Status s = Status::OK();
265     for (int i = 0; i < 10; i++) {
266       prefixes[i] += key.ToString();
267       prefixes[i].resize(prefix_to_use);
268       prefix_slices[i] = Slice(prefixes[i]);
269       readoptionscopy[i] = readoptions;
270       readoptionscopy[i].snapshot = snapshot;
271       if (thread->rand.OneIn(2) &&
272           GetNextPrefix(prefix_slices[i], &(upper_bounds[i]))) {
273         // For half of the time, set the upper bound to the next prefix
274         ub_slices[i] = Slice(upper_bounds[i]);
275         readoptionscopy[i].iterate_upper_bound = &(ub_slices[i]);
276       }
277       iters[i] = db_->NewIterator(readoptionscopy[i], cfh);
278       iters[i]->Seek(prefix_slices[i]);
279     }
280 
281     long count = 0;
282     while (iters[0]->Valid() && iters[0]->key().starts_with(prefix_slices[0])) {
283       count++;
284       std::string values[10];
285       // get list of all values for this iteration
286       for (int i = 0; i < 10; i++) {
287         // no iterator should finish before the first one
288         assert(iters[i]->Valid() &&
289                iters[i]->key().starts_with(prefix_slices[i]));
290         values[i] = iters[i]->value().ToString();
291 
292         char expected_first = (prefixes[i])[0];
293         char actual_first = (values[i])[0];
294 
295         if (actual_first != expected_first) {
296           fprintf(stderr, "error expected first = %c actual = %c\n",
297                   expected_first, actual_first);
298         }
299         (values[i])[0] = ' ';  // blank out the differing character
300       }
301       // make sure all values are equivalent
302       for (int i = 0; i < 10; i++) {
303         if (values[i] != values[0]) {
304           fprintf(stderr,
305                   "error : %d, inconsistent values for prefix %s: %s, %s\n", i,
306                   prefixes[i].c_str(), StringToHex(values[0]).c_str(),
307                   StringToHex(values[i]).c_str());
308           // we continue after error rather than exiting so that we can
309           // find more errors if any
310         }
311         iters[i]->Next();
312       }
313     }
314 
315     // cleanup iterators and snapshot
316     for (int i = 0; i < 10; i++) {
317       // if the first iterator finished, they should have all finished
318       assert(!iters[i]->Valid() ||
319              !iters[i]->key().starts_with(prefix_slices[i]));
320       assert(iters[i]->status().ok());
321       delete iters[i];
322     }
323     db_->ReleaseSnapshot(snapshot);
324 
325     if (s.ok()) {
326       thread->stats.AddPrefixes(1, count);
327     } else {
328       fprintf(stderr, "TestPrefixScan error: %s\n", s.ToString().c_str());
329       thread->stats.AddErrors(1);
330     }
331 
332     return s;
333   }
334 
VerifyDb(ThreadState *) const335   void VerifyDb(ThreadState* /* thread */) const override {}
336 };
337 
CreateBatchedOpsStressTest()338 StressTest* CreateBatchedOpsStressTest() { return new BatchedOpsStressTest(); }
339 
340 }  // namespace ROCKSDB_NAMESPACE
341 #endif  // GFLAGS
342