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