Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / tools / rbd / action / Bench.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "tools/rbd/ArgumentTypes.h"
5 #include "tools/rbd/Shell.h"
6 #include "tools/rbd/Utils.h"
7 #include "common/errno.h"
8 #include "common/strtol.h"
9 #include "common/Cond.h"
10 #include "common/Mutex.h"
11 #include <iostream>
12 #include <boost/accumulators/accumulators.hpp>
13 #include <boost/accumulators/statistics/stats.hpp>
14 #include <boost/accumulators/statistics/rolling_sum.hpp>
15 #include <boost/program_options.hpp>
16
17 namespace rbd {
18 namespace action {
19 namespace bench {
20
21 namespace at = argument_types;
22 namespace po = boost::program_options;
23
24 namespace {
25
26 enum io_type_t {
27   IO_TYPE_READ = 0,
28   IO_TYPE_WRITE,
29
30   IO_TYPE_NUM,
31 };
32
33 struct IOType {};
34 struct Size {};
35 struct IOPattern {};
36
37 void validate(boost::any& v, const std::vector<std::string>& values,
38               Size *target_type, int) {
39   po::validators::check_first_occurrence(v);
40   const std::string &s = po::validators::get_single_string(values);
41
42   std::string parse_error;
43   uint64_t size = strict_sistrtoll(s.c_str(), &parse_error);
44   if (!parse_error.empty()) {
45     throw po::validation_error(po::validation_error::invalid_option_value);
46   }
47   v = boost::any(size);
48 }
49
50 void validate(boost::any& v, const std::vector<std::string>& values,
51               IOPattern *target_type, int) {
52   po::validators::check_first_occurrence(v);
53   const std::string &s = po::validators::get_single_string(values);
54   if (s == "rand") {
55     v = boost::any(true);
56   } else if (s == "seq") {
57     v = boost::any(false);
58   } else {
59     throw po::validation_error(po::validation_error::invalid_option_value);
60   }
61 }
62
63 io_type_t get_io_type(string io_type_string) {
64   if (io_type_string == "read")
65     return IO_TYPE_READ;
66   else if (io_type_string == "write")
67     return IO_TYPE_WRITE;
68   else
69     return IO_TYPE_NUM;
70 }
71
72 void validate(boost::any& v, const std::vector<std::string>& values,
73               IOType *target_type, int) {
74   po::validators::check_first_occurrence(v);
75   const std::string &s = po::validators::get_single_string(values);
76   io_type_t io_type = get_io_type(s);
77   if (io_type >= IO_TYPE_NUM)
78     throw po::validation_error(po::validation_error::invalid_option_value);
79   else
80     v = boost::any(io_type);
81 }
82
83 } // anonymous namespace
84
85 static void rbd_bencher_completion(void *c, void *pc);
86 struct rbd_bencher;
87
88 struct bencher_completer {
89   rbd_bencher *bencher;
90   bufferlist *bl;
91
92 public:
93   bencher_completer(rbd_bencher *bencher, bufferlist *bl)
94     : bencher(bencher), bl(bl)
95   { }
96
97   ~bencher_completer()
98   {
99     if (bl)
100       delete bl;
101   }
102 };
103
104 struct rbd_bencher {
105   librbd::Image *image;
106   Mutex lock;
107   Cond cond;
108   int in_flight;
109   io_type_t io_type;
110   uint64_t io_size;
111   bufferlist write_bl;
112
113   explicit rbd_bencher(librbd::Image *i, io_type_t io_type, uint64_t io_size)
114     : image(i),
115       lock("rbd_bencher::lock"),
116       in_flight(0),
117       io_type(io_type),
118       io_size(io_size)
119   {
120     if (io_type == IO_TYPE_WRITE) {
121       bufferptr bp(io_size);
122       memset(bp.c_str(), rand() & 0xff, io_size);
123       write_bl.push_back(bp);
124     }
125   }
126
127
128   bool start_io(int max, uint64_t off, uint64_t len, int op_flags)
129   {
130     {
131       Mutex::Locker l(lock);
132       if (in_flight >= max)
133         return false;
134       in_flight++;
135     }
136
137     librbd::RBD::AioCompletion *c;
138     if (io_type == IO_TYPE_READ) {
139       bufferlist *read_bl = new bufferlist();
140       c = new librbd::RBD::AioCompletion((void *)(new bencher_completer(this, read_bl)),
141                                          rbd_bencher_completion);
142       image->aio_read2(off, len, *read_bl, c, op_flags);
143     } else if (io_type == IO_TYPE_WRITE) {
144       c = new librbd::RBD::AioCompletion((void *)(new bencher_completer(this, NULL)),
145                                          rbd_bencher_completion);
146       image->aio_write2(off, len, write_bl, c, op_flags);
147     } else {
148       assert(0 == "Invalid io_type");
149     }
150     //cout << "start " << c << " at " << off << "~" << len << std::endl;
151     return true;
152   }
153
154   void wait_for(int max) {
155     Mutex::Locker l(lock);
156     while (in_flight > max) {
157       utime_t dur;
158       dur.set_from_double(.2);
159       cond.WaitInterval(lock, dur);
160     }
161   }
162
163 };
164
165 void rbd_bencher_completion(void *vc, void *pc)
166 {
167   librbd::RBD::AioCompletion *c = (librbd::RBD::AioCompletion *)vc;
168   bencher_completer *bc = static_cast<bencher_completer *>(pc);
169   rbd_bencher *b = bc->bencher;
170   //cout << "complete " << c << std::endl;
171   int ret = c->get_return_value();
172   if (b->io_type == IO_TYPE_WRITE && ret != 0) {
173     cout << "write error: " << cpp_strerror(ret) << std::endl;
174     exit(ret < 0 ? -ret : ret);
175   } else if (b->io_type == IO_TYPE_READ && (unsigned int)ret != b->io_size) {
176     cout << "read error: " << cpp_strerror(ret) << std::endl;
177     exit(ret < 0 ? -ret : ret);
178   }
179   b->lock.Lock();
180   b->in_flight--;
181   b->cond.Signal();
182   b->lock.Unlock();
183   c->release();
184   delete bc;
185 }
186
187 int do_bench(librbd::Image& image, io_type_t io_type,
188                    uint64_t io_size, uint64_t io_threads,
189                    uint64_t io_bytes, bool random)
190 {
191   uint64_t size = 0;
192   image.size(&size);
193   if (io_size > size) {
194     std::cerr << "rbd: io-size " << prettybyte_t(io_size) << " "
195               << "larger than image size " << prettybyte_t(size) << std::endl;
196     return -EINVAL;
197   }
198
199   if (io_size > std::numeric_limits<uint32_t>::max()) {
200     std::cerr << "rbd: io-size should be less than 4G" << std::endl;
201     return -EINVAL;
202   }
203
204   rbd_bencher b(&image, io_type, io_size);
205
206   std::cout << "bench "
207        << " type " << (io_type == IO_TYPE_READ ? "read" : "write")
208        << " io_size " << io_size
209        << " io_threads " << io_threads
210        << " bytes " << io_bytes
211        << " pattern " << (random ? "random" : "sequential")
212        << std::endl;
213
214   srand(time(NULL) % (unsigned long) -1);
215
216   utime_t start = ceph_clock_now();
217   utime_t last;
218   unsigned ios = 0;
219
220   vector<uint64_t> thread_offset;
221   uint64_t i;
222   uint64_t start_pos;
223
224   // disturb all thread's offset, used by seq IO
225   for (i = 0; i < io_threads; i++) {
226     start_pos = (rand() % (size / io_size)) * io_size;
227     thread_offset.push_back(start_pos);
228   }
229
230   const int WINDOW_SIZE = 5;
231   typedef boost::accumulators::accumulator_set<
232     double, boost::accumulators::stats<
233       boost::accumulators::tag::rolling_sum> > RollingSum;
234
235   RollingSum time_acc(
236     boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE);
237   RollingSum ios_acc(
238     boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE);
239   RollingSum off_acc(
240     boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE);
241   uint64_t cur_ios = 0;
242   uint64_t cur_off = 0;
243
244   int op_flags;
245   if  (random) {
246     op_flags = LIBRADOS_OP_FLAG_FADVISE_RANDOM;
247   } else {
248     op_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL;
249   }
250
251   printf("  SEC       OPS   OPS/SEC   BYTES/SEC\n");
252   uint64_t off;
253   for (off = 0; off < io_bytes; ) {
254     b.wait_for(io_threads - 1);
255     i = 0;
256     while (i < io_threads && off < io_bytes) {
257       if (!b.start_io(io_threads, thread_offset[i], io_size, op_flags)) {
258         break;
259       }
260       if (random) {
261         thread_offset[i] = (rand() % (size / io_size)) * io_size;
262       } else {
263         thread_offset[i] += io_size;
264         if (thread_offset[i] + io_size > size)
265           thread_offset[i] = 0;
266       }
267       ++i;
268       ++ios;
269       off += io_size;
270
271       ++cur_ios;
272       cur_off += io_size;
273     }
274
275     utime_t now = ceph_clock_now();
276     utime_t elapsed = now - start;
277     if (last.is_zero()) {
278       last = elapsed;
279     } else if (elapsed.sec() != last.sec()) {
280       time_acc(elapsed - last);
281       ios_acc(static_cast<double>(cur_ios));
282       off_acc(static_cast<double>(cur_off));
283       cur_ios = 0;
284       cur_off = 0;
285
286       double time_sum = boost::accumulators::rolling_sum(time_acc);
287       printf("%5d  %8d  %8.2lf  %8.2lf\n",
288              (int)elapsed,
289              (int)(ios - io_threads),
290              boost::accumulators::rolling_sum(ios_acc) / time_sum,
291              boost::accumulators::rolling_sum(off_acc) / time_sum);
292       last = elapsed;
293     }
294   }
295   b.wait_for(0);
296   int r = image.flush();
297   if (r < 0) {
298     std::cerr << "Error flushing data at the end: " << cpp_strerror(r)
299               << std::endl;
300   }
301
302   utime_t now = ceph_clock_now();
303   double elapsed = now - start;
304
305   printf("elapsed: %5d  ops: %8d  ops/sec: %8.2lf  bytes/sec: %8.2lf\n",
306          (int)elapsed, ios, (double)ios / elapsed, (double)off / elapsed);
307
308   return 0;
309 }
310
311 void add_bench_common_options(po::options_description *positional,
312                               po::options_description *options) {
313   at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE);
314
315   options->add_options()
316     ("io-size", po::value<Size>(), "IO size (in B/K/M/G/T) [default: 4K]")
317     ("io-threads", po::value<uint32_t>(), "ios in flight [default: 16]")
318     ("io-total", po::value<Size>(), "total size for IO (in B/K/M/G/T) [default: 1G]")
319     ("io-pattern", po::value<IOPattern>(), "IO pattern (rand or seq) [default: seq]");
320 }
321
322 void get_arguments_for_write(po::options_description *positional,
323                              po::options_description *options) {
324   add_bench_common_options(positional, options);
325 }
326
327 void get_arguments_for_bench(po::options_description *positional,
328                              po::options_description *options) {
329   add_bench_common_options(positional, options);
330
331   options->add_options()
332     ("io-type", po::value<IOType>()->required(), "IO type (read or write)");
333 }
334
335 int bench_execute(const po::variables_map &vm, io_type_t bench_io_type) {
336   size_t arg_index = 0;
337   std::string pool_name;
338   std::string image_name;
339   std::string snap_name;
340   utils::SnapshotPresence snap_presence = utils::SNAPSHOT_PRESENCE_NONE;
341   if (bench_io_type == IO_TYPE_READ)
342     snap_presence = utils::SNAPSHOT_PRESENCE_PERMITTED;
343
344   int r = utils::get_pool_image_snapshot_names(
345     vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name,
346     &snap_name, snap_presence, utils::SPEC_VALIDATION_NONE);
347   if (r < 0) {
348     return r;
349   }
350
351   uint64_t bench_io_size;
352   if (vm.count("io-size")) {
353     bench_io_size = vm["io-size"].as<uint64_t>();
354   } else {
355     bench_io_size = 4096;
356   }
357   if (bench_io_size == 0) {
358     std::cerr << "rbd: --io-size should be greater than zero." << std::endl;
359     return -EINVAL;
360   }
361
362   uint32_t bench_io_threads;
363   if (vm.count("io-threads")) {
364     bench_io_threads = vm["io-threads"].as<uint32_t>();
365   } else {
366     bench_io_threads = 16;
367   }
368   if (bench_io_threads == 0) {
369     std::cerr << "rbd: --io-threads should be greater than zero." << std::endl;
370     return -EINVAL;
371   }
372
373   uint64_t bench_bytes;
374   if (vm.count("io-total")) {
375     bench_bytes = vm["io-total"].as<uint64_t>();
376   } else {
377     bench_bytes = 1 << 30;
378   }
379
380   bool bench_random;
381   if (vm.count("io-pattern")) {
382     bench_random = vm["io-pattern"].as<bool>();
383   } else {
384     bench_random = false;
385   }
386
387   librados::Rados rados;
388   librados::IoCtx io_ctx;
389   librbd::Image image;
390   r = utils::init_and_open_image(pool_name, image_name, "", "", false, &rados,
391                                  &io_ctx, &image);
392   if (r < 0) {
393     return r;
394   }
395
396   r = do_bench(image, bench_io_type, bench_io_size, bench_io_threads,
397                      bench_bytes, bench_random);
398   if (r < 0) {
399     std::cerr << "bench failed: " << cpp_strerror(r) << std::endl;
400     return r;
401   }
402   return 0;
403 }
404
405 int execute_for_write(const po::variables_map &vm) {
406   std::cerr << "rbd: bench-write is deprecated, use rbd bench --io-type write ..." << std::endl;
407   return bench_execute(vm, IO_TYPE_WRITE);
408 }
409
410 int execute_for_bench(const po::variables_map &vm) {
411   io_type_t bench_io_type;
412   if (vm.count("io-type")) {
413     bench_io_type = vm["io-type"].as<io_type_t>();
414   } else {
415     std::cerr << "rbd: --io-type must be specified." << std::endl;
416     return -EINVAL;
417   }
418
419   return bench_execute(vm, bench_io_type);
420 }
421
422 Shell::Action action_write(
423   {"bench-write"}, {}, "Simple write benchmark. (Deprecated, please use `rbd bench --io-type write` instead.)",
424                    "", &get_arguments_for_write, &execute_for_write, false);
425
426 Shell::Action action_bench(
427   {"bench"}, {}, "Simple benchmark.", "", &get_arguments_for_bench, &execute_for_bench);
428
429 } // namespace bench
430 } // namespace action
431 } // namespace rbd