]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/rbd/action/Bench.cc
update sources to v12.1.0
[ceph.git] / ceph / src / tools / rbd / action / Bench.cc
CommitLineData
7c673cae
FG
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
17namespace rbd {
18namespace action {
19namespace bench {
20
21namespace at = argument_types;
22namespace po = boost::program_options;
23
24namespace {
25
26enum io_type_t {
27 IO_TYPE_READ = 0,
28 IO_TYPE_WRITE,
29
30 IO_TYPE_NUM,
31};
32
33struct IOType {};
34struct Size {};
35struct IOPattern {};
36
37void 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
50void 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
63io_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
72void 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
85static void rbd_bencher_completion(void *c, void *pc);
86struct rbd_bencher;
87
88struct bencher_completer {
89 rbd_bencher *bencher;
90 bufferlist *bl;
91
92public:
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
104struct 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
165void 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
187int 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) {
31f18b77
FG
257 if (!b.start_io(io_threads, thread_offset[i], io_size, op_flags)) {
258 break;
259 }
7c673cae
FG
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 }
7c673cae
FG
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
311void 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
322void get_arguments_for_write(po::options_description *positional,
323 po::options_description *options) {
324 add_bench_common_options(positional, options);
325}
326
327void 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
335int 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
405int 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
410int 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
422Shell::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
426Shell::Action action_bench(
427 {"bench"}, {}, "Simple benchmark.", "", &get_arguments_for_bench, &execute_for_bench);
428
429} // namespace bench
430} // namespace action
431} // namespace rbd