1// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file. See the AUTHORS file for names of contributors.
4
5#include <stdio.h>
6#include <stdlib.h>
7#include <kcpolydb.h>
8#include "util/histogram.h"
9#include "util/random.h"
10#include "util/testutil.h"
11
12// Comma-separated list of operations to run in the specified order
13//   Actual benchmarks:
14//
15//   fillseq       -- write N values in sequential key order in async mode
16//   fillrandom    -- write N values in random key order in async mode
17//   overwrite     -- overwrite N values in random key order in async mode
18//   fillseqsync   -- write N/100 values in sequential key order in sync mode
19//   fillrandsync  -- write N/100 values in random key order in sync mode
20//   fillrand100K  -- write N/1000 100K values in random order in async mode
21//   fillseq100K   -- write N/1000 100K values in seq order in async mode
22//   readseq       -- read N times sequentially
23//   readseq100K   -- read N/1000 100K values in sequential order in async mode
24//   readrand100K  -- read N/1000 100K values in sequential order in async mode
25//   readrandom    -- read N times in random order
26static const char* FLAGS_benchmarks =
27    "fillseq,"
28    "fillseqsync,"
29    "fillrandsync,"
30    "fillrandom,"
31    "overwrite,"
32    "readrandom,"
33    "readseq,"
34    "fillrand100K,"
35    "fillseq100K,"
36    "readseq100K,"
37    "readrand100K,"
38    ;
39
40// Number of key/values to place in database
41static int FLAGS_num = 1000000;
42
43// Number of read operations to do.  If negative, do FLAGS_num reads.
44static int FLAGS_reads = -1;
45
46// Size of each value
47static int FLAGS_value_size = 100;
48
49// Arrange to generate values that shrink to this fraction of
50// their original size after compression
51static double FLAGS_compression_ratio = 0.5;
52
53// Print histogram of operation timings
54static bool FLAGS_histogram = false;
55
56// Cache size. Default 4 MB
57static int FLAGS_cache_size = 4194304;
58
59// Page size. Default 1 KB
60static int FLAGS_page_size = 1024;
61
62// If true, do not destroy the existing database.  If you set this
63// flag and also specify a benchmark that wants a fresh database, that
64// benchmark will fail.
65static bool FLAGS_use_existing_db = false;
66
67// Compression flag. If true, compression is on. If false, compression
68// is off.
69static bool FLAGS_compression = true;
70
71// Use the db with the following name.
72static const char* FLAGS_db = NULL;
73
74inline
75static void DBSynchronize(kyotocabinet::TreeDB* db_)
76{
77  // Synchronize will flush writes to disk
78  if (!db_->synchronize()) {
79    fprintf(stderr, "synchronize error: %s\n", db_->error().name());
80  }
81}
82
83namespace leveldb {
84
85// Helper for quickly generating random data.
86namespace {
87class RandomGenerator {
88 private:
89  std::string data_;
90  int pos_;
91
92 public:
93  RandomGenerator() {
94    // We use a limited amount of data over and over again and ensure
95    // that it is larger than the compression window (32KB), and also
96    // large enough to serve all typical value sizes we want to write.
97    Random rnd(301);
98    std::string piece;
99    while (data_.size() < 1048576) {
100      // Add a short fragment that is as compressible as specified
101      // by FLAGS_compression_ratio.
102      test::CompressibleString(&rnd, FLAGS_compression_ratio, 100, &piece);
103      data_.append(piece);
104    }
105    pos_ = 0;
106  }
107
108  Slice Generate(int len) {
109    if (pos_ + len > data_.size()) {
110      pos_ = 0;
111      assert(len < data_.size());
112    }
113    pos_ += len;
114    return Slice(data_.data() + pos_ - len, len);
115  }
116};
117
118static Slice TrimSpace(Slice s) {
119  int start = 0;
120  while (start < s.size() && isspace(s[start])) {
121    start++;
122  }
123  int limit = s.size();
124  while (limit > start && isspace(s[limit-1])) {
125    limit--;
126  }
127  return Slice(s.data() + start, limit - start);
128}
129
130}  // namespace
131
132class Benchmark {
133 private:
134  kyotocabinet::TreeDB* db_;
135  int db_num_;
136  int num_;
137  int reads_;
138  double start_;
139  double last_op_finish_;
140  int64_t bytes_;
141  std::string message_;
142  Histogram hist_;
143  RandomGenerator gen_;
144  Random rand_;
145  kyotocabinet::LZOCompressor<kyotocabinet::LZO::RAW> comp_;
146
147  // State kept for progress messages
148  int done_;
149  int next_report_;     // When to report next
150
151  void PrintHeader() {
152    const int kKeySize = 16;
153    PrintEnvironment();
154    fprintf(stdout, "Keys:       %d bytes each\n", kKeySize);
155    fprintf(stdout, "Values:     %d bytes each (%d bytes after compression)\n",
156            FLAGS_value_size,
157            static_cast<int>(FLAGS_value_size * FLAGS_compression_ratio + 0.5));
158    fprintf(stdout, "Entries:    %d\n", num_);
159    fprintf(stdout, "RawSize:    %.1f MB (estimated)\n",
160            ((static_cast<int64_t>(kKeySize + FLAGS_value_size) * num_)
161             / 1048576.0));
162    fprintf(stdout, "FileSize:   %.1f MB (estimated)\n",
163            (((kKeySize + FLAGS_value_size * FLAGS_compression_ratio) * num_)
164             / 1048576.0));
165    PrintWarnings();
166    fprintf(stdout, "------------------------------------------------\n");
167  }
168
169  void PrintWarnings() {
170#if defined(__GNUC__) && !defined(__OPTIMIZE__)
171    fprintf(stdout,
172            "WARNING: Optimization is disabled: benchmarks unnecessarily slow\n"
173            );
174#endif
175#ifndef NDEBUG
176    fprintf(stdout,
177            "WARNING: Assertions are enabled; benchmarks unnecessarily slow\n");
178#endif
179  }
180
181  void PrintEnvironment() {
182    fprintf(stderr, "Kyoto Cabinet:    version %s, lib ver %d, lib rev %d\n",
183            kyotocabinet::VERSION, kyotocabinet::LIBVER, kyotocabinet::LIBREV);
184
185#if defined(__linux)
186    time_t now = time(NULL);
187    fprintf(stderr, "Date:           %s", ctime(&now));  // ctime() adds newline
188
189    FILE* cpuinfo = fopen("/proc/cpuinfo", "r");
190    if (cpuinfo != NULL) {
191      char line[1000];
192      int num_cpus = 0;
193      std::string cpu_type;
194      std::string cache_size;
195      while (fgets(line, sizeof(line), cpuinfo) != NULL) {
196        const char* sep = strchr(line, ':');
197        if (sep == NULL) {
198          continue;
199        }
200        Slice key = TrimSpace(Slice(line, sep - 1 - line));
201        Slice val = TrimSpace(Slice(sep + 1));
202        if (key == "model name") {
203          ++num_cpus;
204          cpu_type = val.ToString();
205        } else if (key == "cache size") {
206          cache_size = val.ToString();
207        }
208      }
209      fclose(cpuinfo);
210      fprintf(stderr, "CPU:            %d * %s\n", num_cpus, cpu_type.c_str());
211      fprintf(stderr, "CPUCache:       %s\n", cache_size.c_str());
212    }
213#endif
214  }
215
216  void Start() {
217    start_ = Env::Default()->NowMicros() * 1e-6;
218    bytes_ = 0;
219    message_.clear();
220    last_op_finish_ = start_;
221    hist_.Clear();
222    done_ = 0;
223    next_report_ = 100;
224  }
225
226  void FinishedSingleOp() {
227    if (FLAGS_histogram) {
228      double now = Env::Default()->NowMicros() * 1e-6;
229      double micros = (now - last_op_finish_) * 1e6;
230      hist_.Add(micros);
231      if (micros > 20000) {
232        fprintf(stderr, "long op: %.1f micros%30s\r", micros, "");
233        fflush(stderr);
234      }
235      last_op_finish_ = now;
236    }
237
238    done_++;
239    if (done_ >= next_report_) {
240      if      (next_report_ < 1000)   next_report_ += 100;
241      else if (next_report_ < 5000)   next_report_ += 500;
242      else if (next_report_ < 10000)  next_report_ += 1000;
243      else if (next_report_ < 50000)  next_report_ += 5000;
244      else if (next_report_ < 100000) next_report_ += 10000;
245      else if (next_report_ < 500000) next_report_ += 50000;
246      else                            next_report_ += 100000;
247      fprintf(stderr, "... finished %d ops%30s\r", done_, "");
248      fflush(stderr);
249    }
250  }
251
252  void Stop(const Slice& name) {
253    double finish = Env::Default()->NowMicros() * 1e-6;
254
255    // Pretend at least one op was done in case we are running a benchmark
256    // that does not call FinishedSingleOp().
257    if (done_ < 1) done_ = 1;
258
259    if (bytes_ > 0) {
260      char rate[100];
261      snprintf(rate, sizeof(rate), "%6.1f MB/s",
262               (bytes_ / 1048576.0) / (finish - start_));
263      if (!message_.empty()) {
264        message_  = std::string(rate) + " " + message_;
265      } else {
266        message_ = rate;
267      }
268    }
269
270    fprintf(stdout, "%-12s : %11.3f micros/op;%s%s\n",
271            name.ToString().c_str(),
272            (finish - start_) * 1e6 / done_,
273            (message_.empty() ? "" : " "),
274            message_.c_str());
275    if (FLAGS_histogram) {
276      fprintf(stdout, "Microseconds per op:\n%s\n", hist_.ToString().c_str());
277    }
278    fflush(stdout);
279  }
280
281 public:
282  enum Order {
283    SEQUENTIAL,
284    RANDOM
285  };
286  enum DBState {
287    FRESH,
288    EXISTING
289  };
290
291  Benchmark()
292  : db_(NULL),
293    num_(FLAGS_num),
294    reads_(FLAGS_reads < 0 ? FLAGS_num : FLAGS_reads),
295    bytes_(0),
296    rand_(301) {
297    std::vector<std::string> files;
298    std::string test_dir;
299    Env::Default()->GetTestDirectory(&test_dir);
300    Env::Default()->GetChildren(test_dir.c_str(), &files);
301    if (!FLAGS_use_existing_db) {
302      for (int i = 0; i < files.size(); i++) {
303        if (Slice(files[i]).starts_with("dbbench_polyDB")) {
304          std::string file_name(test_dir);
305          file_name += "/";
306          file_name += files[i];
307          Env::Default()->DeleteFile(file_name.c_str());
308        }
309      }
310    }
311  }
312
313  ~Benchmark() {
314    if (!db_->close()) {
315      fprintf(stderr, "close error: %s\n", db_->error().name());
316    }
317  }
318
319  void Run() {
320    PrintHeader();
321    Open(false);
322
323    const char* benchmarks = FLAGS_benchmarks;
324    while (benchmarks != NULL) {
325      const char* sep = strchr(benchmarks, ',');
326      Slice name;
327      if (sep == NULL) {
328        name = benchmarks;
329        benchmarks = NULL;
330      } else {
331        name = Slice(benchmarks, sep - benchmarks);
332        benchmarks = sep + 1;
333      }
334
335      Start();
336
337      bool known = true;
338      bool write_sync = false;
339      if (name == Slice("fillseq")) {
340        Write(write_sync, SEQUENTIAL, FRESH, num_, FLAGS_value_size, 1);
341
342      } else if (name == Slice("fillrandom")) {
343        Write(write_sync, RANDOM, FRESH, num_, FLAGS_value_size, 1);
344        DBSynchronize(db_);
345      } else if (name == Slice("overwrite")) {
346        Write(write_sync, RANDOM, EXISTING, num_, FLAGS_value_size, 1);
347        DBSynchronize(db_);
348      } else if (name == Slice("fillrandsync")) {
349        write_sync = true;
350        Write(write_sync, RANDOM, FRESH, num_ / 100, FLAGS_value_size, 1);
351        DBSynchronize(db_);
352      } else if (name == Slice("fillseqsync")) {
353        write_sync = true;
354        Write(write_sync, SEQUENTIAL, FRESH, num_ / 100, FLAGS_value_size, 1);
355        DBSynchronize(db_);
356      } else if (name == Slice("fillrand100K")) {
357        Write(write_sync, RANDOM, FRESH, num_ / 1000, 100 * 1000, 1);
358        DBSynchronize(db_);
359      } else if (name == Slice("fillseq100K")) {
360        Write(write_sync, SEQUENTIAL, FRESH, num_ / 1000, 100 * 1000, 1);
361        DBSynchronize(db_);
362      } else if (name == Slice("readseq")) {
363        ReadSequential();
364      } else if (name == Slice("readrandom")) {
365        ReadRandom();
366      } else if (name == Slice("readrand100K")) {
367        int n = reads_;
368        reads_ /= 1000;
369        ReadRandom();
370        reads_ = n;
371      } else if (name == Slice("readseq100K")) {
372        int n = reads_;
373        reads_ /= 1000;
374        ReadSequential();
375        reads_ = n;
376      } else {
377        known = false;
378        if (name != Slice()) {  // No error message for empty name
379          fprintf(stderr, "unknown benchmark '%s'\n", name.ToString().c_str());
380        }
381      }
382      if (known) {
383        Stop(name);
384      }
385    }
386  }
387
388 private:
389    void Open(bool sync) {
390    assert(db_ == NULL);
391
392    // Initialize db_
393    db_ = new kyotocabinet::TreeDB();
394    char file_name[100];
395    db_num_++;
396    std::string test_dir;
397    Env::Default()->GetTestDirectory(&test_dir);
398    snprintf(file_name, sizeof(file_name),
399             "%s/dbbench_polyDB-%d.kct",
400             test_dir.c_str(),
401             db_num_);
402
403    // Create tuning options and open the database
404    int open_options = kyotocabinet::PolyDB::OWRITER |
405                       kyotocabinet::PolyDB::OCREATE;
406    int tune_options = kyotocabinet::TreeDB::TSMALL |
407        kyotocabinet::TreeDB::TLINEAR;
408    if (FLAGS_compression) {
409      tune_options |= kyotocabinet::TreeDB::TCOMPRESS;
410      db_->tune_compressor(&comp_);
411    }
412    db_->tune_options(tune_options);
413    db_->tune_page_cache(FLAGS_cache_size);
414    db_->tune_page(FLAGS_page_size);
415    db_->tune_map(256LL<<20);
416    if (sync) {
417      open_options |= kyotocabinet::PolyDB::OAUTOSYNC;
418    }
419    if (!db_->open(file_name, open_options)) {
420      fprintf(stderr, "open error: %s\n", db_->error().name());
421    }
422  }
423
424  void Write(bool sync, Order order, DBState state,
425             int num_entries, int value_size, int entries_per_batch) {
426    // Create new database if state == FRESH
427    if (state == FRESH) {
428      if (FLAGS_use_existing_db) {
429        message_ = "skipping (--use_existing_db is true)";
430        return;
431      }
432      delete db_;
433      db_ = NULL;
434      Open(sync);
435      Start();  // Do not count time taken to destroy/open
436    }
437
438    if (num_entries != num_) {
439      char msg[100];
440      snprintf(msg, sizeof(msg), "(%d ops)", num_entries);
441      message_ = msg;
442    }
443
444    // Write to database
445    for (int i = 0; i < num_entries; i++)
446    {
447      const int k = (order == SEQUENTIAL) ? i : (rand_.Next() % num_entries);
448      char key[100];
449      snprintf(key, sizeof(key), "%016d", k);
450      bytes_ += value_size + strlen(key);
451      std::string cpp_key = key;
452      if (!db_->set(cpp_key, gen_.Generate(value_size).ToString())) {
453        fprintf(stderr, "set error: %s\n", db_->error().name());
454      }
455      FinishedSingleOp();
456    }
457  }
458
459  void ReadSequential() {
460    kyotocabinet::DB::Cursor* cur = db_->cursor();
461    cur->jump();
462    std::string ckey, cvalue;
463    while (cur->get(&ckey, &cvalue, true)) {
464      bytes_ += ckey.size() + cvalue.size();
465      FinishedSingleOp();
466    }
467    delete cur;
468  }
469
470  void ReadRandom() {
471    std::string value;
472    for (int i = 0; i < reads_; i++) {
473      char key[100];
474      const int k = rand_.Next() % reads_;
475      snprintf(key, sizeof(key), "%016d", k);
476      db_->get(key, &value);
477      FinishedSingleOp();
478    }
479  }
480};
481
482}  // namespace leveldb
483
484int main(int argc, char** argv) {
485  std::string default_db_path;
486  for (int i = 1; i < argc; i++) {
487    double d;
488    int n;
489    char junk;
490    if (leveldb::Slice(argv[i]).starts_with("--benchmarks=")) {
491      FLAGS_benchmarks = argv[i] + strlen("--benchmarks=");
492    } else if (sscanf(argv[i], "--compression_ratio=%lf%c", &d, &junk) == 1) {
493      FLAGS_compression_ratio = d;
494    } else if (sscanf(argv[i], "--histogram=%d%c", &n, &junk) == 1 &&
495               (n == 0 || n == 1)) {
496      FLAGS_histogram = n;
497    } else if (sscanf(argv[i], "--num=%d%c", &n, &junk) == 1) {
498      FLAGS_num = n;
499    } else if (sscanf(argv[i], "--reads=%d%c", &n, &junk) == 1) {
500      FLAGS_reads = n;
501    } else if (sscanf(argv[i], "--value_size=%d%c", &n, &junk) == 1) {
502      FLAGS_value_size = n;
503    } else if (sscanf(argv[i], "--cache_size=%d%c", &n, &junk) == 1) {
504      FLAGS_cache_size = n;
505    } else if (sscanf(argv[i], "--page_size=%d%c", &n, &junk) == 1) {
506      FLAGS_page_size = n;
507    } else if (sscanf(argv[i], "--compression=%d%c", &n, &junk) == 1 &&
508               (n == 0 || n == 1)) {
509      FLAGS_compression = (n == 1) ? true : false;
510    } else if (strncmp(argv[i], "--db=", 5) == 0) {
511      FLAGS_db = argv[i] + 5;
512    } else {
513      fprintf(stderr, "Invalid flag '%s'\n", argv[i]);
514      exit(1);
515    }
516  }
517
518  // Choose a location for the test database if none given with --db=<path>
519  if (FLAGS_db == NULL) {
520      leveldb::Env::Default()->GetTestDirectory(&default_db_path);
521      default_db_path += "/dbbench";
522      FLAGS_db = default_db_path.c_str();
523  }
524
525  leveldb::Benchmark benchmark;
526  benchmark.Run();
527  return 0;
528}
529