]>
Commit | Line | Data |
---|---|---|
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 | ||
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) { | |
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 | ||
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 |