]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/rbd/action/Perf.cc
update download target update for octopus release
[ceph.git] / ceph / src / tools / rbd / action / Perf.cc
CommitLineData
11fdf7f2
TL
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 "include/stringify.h"
8#include "common/ceph_context.h"
9#include "common/ceph_json.h"
10#include "common/errno.h"
11#include "common/Formatter.h"
12#include "common/TextTable.h"
13#include "global/global_context.h"
14#include <ncurses.h>
15#include <stdio.h>
16#include <unistd.h>
17#include <sys/time.h>
18#include <sys/types.h>
19#include <iostream>
20#include <vector>
21#include <boost/algorithm/string.hpp>
22#include <boost/assign.hpp>
23#include <boost/bimap.hpp>
24#include <boost/program_options.hpp>
25
26namespace rbd {
27namespace action {
28namespace perf {
29
30namespace at = argument_types;
31namespace po = boost::program_options;
32
33namespace {
34
35enum class StatDescriptor {
36 WRITE_OPS = 0,
37 READ_OPS,
38 WRITE_BYTES,
39 READ_BYTES,
40 WRITE_LATENCY,
41 READ_LATENCY
42};
43
44typedef boost::bimap<StatDescriptor, std::string> StatDescriptors;
45
46static const StatDescriptors STAT_DESCRIPTORS =
47 boost::assign::list_of<StatDescriptors::relation>
48 (StatDescriptor::WRITE_OPS, "write_ops")
49 (StatDescriptor::READ_OPS, "read_ops")
50 (StatDescriptor::WRITE_BYTES, "write_bytes")
51 (StatDescriptor::READ_BYTES, "read_bytes")
52 (StatDescriptor::WRITE_LATENCY, "write_latency")
53 (StatDescriptor::READ_LATENCY, "read_latency");
54
55std::ostream& operator<<(std::ostream& os, const StatDescriptor& val) {
56 auto it = STAT_DESCRIPTORS.left.find(val);
57 if (it == STAT_DESCRIPTORS.left.end()) {
58 os << "unknown (" << static_cast<int>(val) << ")";
59 } else {
60 os << it->second;
61 }
62 return os;
63}
64
65void validate(boost::any& v, const std::vector<std::string>& values,
66 StatDescriptor *target_type, int) {
67 po::validators::check_first_occurrence(v);
68 std::string s = po::validators::get_single_string(values);
69 boost::replace_all(s, "_", " ");
70 boost::replace_all(s, "-", "_");
71
72 auto it = STAT_DESCRIPTORS.right.find(s);
73 if (it == STAT_DESCRIPTORS.right.end()) {
74 throw po::validation_error(po::validation_error::invalid_option_value);
75 }
76 v = boost::any(it->second);
77}
78
79struct ImageStat {
80 ImageStat(const std::string& pool_name, const std::string& pool_namespace,
81 const std::string& image_name)
82 : pool_name(pool_name), pool_namespace(pool_namespace),
83 image_name(image_name) {
84 stats.resize(STAT_DESCRIPTORS.size());
85 }
86
87 std::string pool_name;
88 std::string pool_namespace;
89 std::string image_name;
90 std::vector<double> stats;
91};
92
93typedef std::vector<ImageStat> ImageStats;
94
95typedef std::pair<std::string, std::string> SpecPair;
96
97std::string format_pool_spec(const std::string& pool,
98 const std::string& pool_namespace) {
99 std::string pool_spec{pool};
100 if (!pool_namespace.empty()) {
101 pool_spec += "/" + pool_namespace;
102 }
103 return pool_spec;
104}
105
106int query_iostats(librados::Rados& rados, const std::string& pool_spec,
107 StatDescriptor sort_by, ImageStats* image_stats,
108 std::ostream& err_os) {
109 auto sort_by_str = STAT_DESCRIPTORS.left.find(sort_by)->second;
110
111 std::string cmd = R"(
112 {
113 "prefix": "rbd perf image stats",
114 "pool_spec": ")" + pool_spec + R"(",
115 "sort_by": ")" + sort_by_str + R"(",
116 "format": "json"
117 }")";
118
119 bufferlist in_bl;
120 bufferlist out_bl;
121 std::string outs;
122 int r = rados.mgr_command(cmd, in_bl, &out_bl, &outs);
123 if (r == -EOPNOTSUPP) {
124 err_os << "rbd: 'rbd_support' mgr module is not enabled."
125 << std::endl << std::endl
126 << "Use 'ceph mgr module enable rbd_support' to enable."
127 << std::endl;
128 return r;
129 } else if (r < 0) {
130 err_os << "rbd: mgr command failed: " << cpp_strerror(r);
131 if (!outs.empty()) {
132 err_os << ": " << outs;
133 }
134 err_os << std::endl;
135 return r;
136 }
137
138 json_spirit::mValue json_root;
139 if (!json_spirit::read(out_bl.to_str(), json_root)) {
140 err_os << "rbd: error parsing perf stats" << std::endl;
141 return -EINVAL;
142 }
143
144 image_stats->clear();
145 try {
146 auto& root = json_root.get_obj();
147
148 // map JSON stat descriptor order to our internal order
149 std::map<uint32_t, uint32_t> json_to_internal_stats;
150 auto& json_stat_descriptors = root["stat_descriptors"].get_array();
151 for (size_t idx = 0; idx < json_stat_descriptors.size(); ++idx) {
152 auto it = STAT_DESCRIPTORS.right.find(
153 json_stat_descriptors[idx].get_str());
154 if (it == STAT_DESCRIPTORS.right.end()) {
155 continue;
156 }
157 json_to_internal_stats[idx] = static_cast<uint32_t>(it->second);
158 }
159
160 // cache a mapping from pool descriptors back to pool-specs
161 std::map<std::string, SpecPair> json_to_internal_pools;
162 auto& pool_descriptors = root["pool_descriptors"].get_obj();
163 for (auto& pool : pool_descriptors) {
164 auto& pool_spec = pool.second.get_str();
165 auto pos = pool_spec.rfind("/");
166
167 SpecPair pair{pool_spec.substr(0, pos), ""};
168 if (pos != std::string::npos) {
169 pair.second = pool_spec.substr(pos + 1);
170 }
171
172 json_to_internal_pools[pool.first] = pair;
173 }
174
175 auto& stats = root["stats"].get_array();
176 for (auto& stat : stats) {
177 auto& stat_obj = stat.get_obj();
178 if (!stat_obj.empty()) {
179 auto& image_spec = stat_obj.begin()->first;
180
181 auto pos = image_spec.find("/");
182 SpecPair pair{image_spec.substr(0, pos), ""};
183 if (pos != std::string::npos) {
184 pair.second = image_spec.substr(pos + 1);
185 }
186
187 const auto pool_it = json_to_internal_pools.find(pair.first);
188 if (pool_it == json_to_internal_pools.end()) {
189 continue;
190 }
191
192 image_stats->emplace_back(
193 pool_it->second.first, pool_it->second.second, pair.second);
194
195 auto& image_stat = image_stats->back();
196 auto& data = stat_obj.begin()->second.get_array();
197 for (auto& indexes : json_to_internal_stats) {
198 image_stat.stats[indexes.second] = data[indexes.first].get_real();
199 }
200 }
201 }
202 } catch (std::runtime_error &e) {
203 err_os << "rbd: error parsing perf stats: " << e.what() << std::endl;
204 return -EINVAL;
205 }
206
207 return 0;
208}
209
210void format_stat(StatDescriptor stat_descriptor, double stat,
211 std::ostream& os) {
212 switch (stat_descriptor) {
213 case StatDescriptor::WRITE_OPS:
214 case StatDescriptor::READ_OPS:
215 os << si_u_t(stat) << "/s";
216 break;
217 case StatDescriptor::WRITE_BYTES:
218 case StatDescriptor::READ_BYTES:
219 os << byte_u_t(stat) << "/s";
220 break;
221 case StatDescriptor::WRITE_LATENCY:
222 case StatDescriptor::READ_LATENCY:
223 os << std::fixed << std::setprecision(2);
224 if (stat >= 1000000000) {
225 os << (stat / 1000000000) << " s";
226 } else if (stat >= 1000000) {
227 os << (stat / 1000000) << " ms";
228 } else if (stat >= 1000) {
229 os << (stat / 1000) << " us";
230 } else {
231 os << stat << " ns";
232 }
233 break;
234 default:
235 ceph_assert(false);
236 break;
237 }
238}
239
240} // anonymous namespace
241
242namespace iostat {
243
244struct Iterations {};
245
246void validate(boost::any& v, const std::vector<std::string>& values,
247 Iterations *target_type, int) {
248 po::validators::check_first_occurrence(v);
249 auto& s = po::validators::get_single_string(values);
250
251 try {
252 auto iterations = boost::lexical_cast<uint32_t>(s);
253 if (iterations > 0) {
254 v = boost::any(iterations);
255 return;
256 }
257 } catch (const boost::bad_lexical_cast &) {
258 }
259 throw po::validation_error(po::validation_error::invalid_option_value);
260}
261
262void format(const ImageStats& image_stats, Formatter* f, bool global_search) {
263 TextTable tbl;
264 if (f) {
265 f->open_array_section("images");
266 } else {
267 tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT);
268 for (auto& stat : STAT_DESCRIPTORS.left) {
269 std::string title;
270 switch (stat.first) {
271 case StatDescriptor::WRITE_OPS:
272 title = "WR ";
273 break;
274 case StatDescriptor::READ_OPS:
275 title = "RD ";
276 break;
277 case StatDescriptor::WRITE_BYTES:
278 title = "WR_BYTES ";
279 break;
280 case StatDescriptor::READ_BYTES:
281 title = "RD_BYTES ";
282 break;
283 case StatDescriptor::WRITE_LATENCY:
284 title = "WR_LAT ";
285 break;
286 case StatDescriptor::READ_LATENCY:
287 title = "RD_LAT ";
288 break;
289 default:
290 ceph_assert(false);
291 break;
292 }
293 tbl.define_column(title, TextTable::RIGHT, TextTable::RIGHT);
294 }
295 }
296
297 for (auto& image_stat : image_stats) {
298 if (f) {
299 f->open_object_section("image");
300 f->dump_string("pool", image_stat.pool_name);
301 f->dump_string("pool_namespace", image_stat.pool_namespace);
302 f->dump_string("image", image_stat.image_name);
303 for (auto& pair : STAT_DESCRIPTORS.left) {
304 f->dump_float(pair.second.c_str(),
305 image_stat.stats[static_cast<size_t>(pair.first)]);
306 }
307 f->close_section();
308 } else {
309 std::string name;
310 if (global_search) {
311 name += image_stat.pool_name + "/";
312 if (!image_stat.pool_namespace.empty()) {
313 name += image_stat.pool_namespace + "/";
314 }
315 }
316 name += image_stat.image_name;
317
318 tbl << name;
319 for (auto& pair : STAT_DESCRIPTORS.left) {
320 std::stringstream str;
321 format_stat(pair.first,
322 image_stat.stats[static_cast<size_t>(pair.first)], str);
323 str << ' ';
324 tbl << str.str();
325 }
326 tbl << TextTable::endrow;
327 }
328 }
329
330 if (f) {
331 f->close_section();
332 f->flush(std::cout);
333 } else {
334 std::cout << tbl << std::endl;
335 }
336}
337
338} // namespace iostat
339
340namespace iotop {
341
342class MainWindow {
343public:
344 MainWindow(librados::Rados& rados, const std::string& pool_spec)
345 : m_rados(rados), m_pool_spec(pool_spec) {
346 initscr();
347 curs_set(0);
348 cbreak();
349 noecho();
350 keypad(stdscr, TRUE);
351 nodelay(stdscr, TRUE);
352
353 init_columns();
354 }
355
356 int run() {
357 redraw();
358
359 int r = 0;
360 std::stringstream err_str;
361 while (true) {
362 r = query_iostats(m_rados, m_pool_spec, m_sort_by, &m_image_stats,
363 err_str);
364 if (r < 0) {
365 break;
366 return r;
367 }
368
369 redraw();
370 wait_for_key_or_delay();
371
372 int ch = getch();
373 if (ch == 'q' || ch == 'Q') {
374 break;
375 } else if (ch == '<' || ch == KEY_LEFT) {
376 auto it = STAT_DESCRIPTORS.left.find(m_sort_by);
377 if (it != STAT_DESCRIPTORS.left.begin()) {
378 m_sort_by = (--it)->first;
379 }
380 } else if (ch == '>' || ch == KEY_RIGHT) {
381 auto it = STAT_DESCRIPTORS.left.find(m_sort_by);
382 if (it != STAT_DESCRIPTORS.left.end() &&
383 ++it != STAT_DESCRIPTORS.left.end()) {
384 m_sort_by = it->first;
385 }
386 }
387 }
388
389 endwin();
390
391 if (r < 0) {
392 std::cerr << err_str.str() << std::endl;
393 }
394 return r;
395 }
396
397private:
398 static const size_t STAT_COLUMN_WIDTH = 12;
399
400 librados::Rados& m_rados;
401 std::string m_pool_spec;
402
403 ImageStats m_image_stats;
404 StatDescriptor m_sort_by = StatDescriptor::WRITE_OPS;
405
406 bool m_pending_win_opened = false;
407 WINDOW* m_pending_win = nullptr;
408
409 int m_height = 1;
410 int m_width = 1;
411
412 std::map<StatDescriptor, std::string> m_columns;
413
414 void init_columns() {
415 m_columns.clear();
416 for (auto& pair : STAT_DESCRIPTORS.left) {
417 std::string title;
418 switch (pair.first) {
419 case StatDescriptor::WRITE_OPS:
420 title = "WRITES OPS";
421 break;
422 case StatDescriptor::READ_OPS:
423 title = "READS OPS";
424 break;
425 case StatDescriptor::WRITE_BYTES:
426 title = "WRITE BYTES";
427 break;
428 case StatDescriptor::READ_BYTES:
429 title = "READ BYTES";
430 break;
431 case StatDescriptor::WRITE_LATENCY:
432 title = "WRITE LAT";
433 break;
434 case StatDescriptor::READ_LATENCY:
435 title = "READ LAT";
436 break;
437 default:
438 ceph_assert(false);
439 break;
440 }
441 m_columns[pair.first] = (title);
442 }
443 }
444
445 void redraw() {
446 getmaxyx(stdscr, m_height, m_width);
447
448 redraw_main_window();
449 redraw_pending_window();
450
451 doupdate();
452 }
453
454 void redraw_main_window() {
455 werase(stdscr);
456 mvhline(0, 0, ' ' | A_REVERSE, m_width);
457
458 // print header for all metrics
459 int remaining_cols = m_width;
460 std::stringstream str;
461 for (auto& pair : m_columns) {
462 int attr = A_REVERSE;
463 std::string title;
464 if (pair.first == m_sort_by) {
465 title += '>';
466 attr |= A_BOLD;
467 } else {
468 title += ' ';
469 }
470 title += pair.second;
471
472 str.str("");
473 str << std::right << std::setfill(' ')
474 << std::setw(STAT_COLUMN_WIDTH)
475 << title << ' ';
476
477 attrset(attr);
478 addstr(str.str().c_str());
479 remaining_cols -= title.size();
480 }
481
482 attrset(A_REVERSE);
483 addstr("IMAGE");
484 attrset(A_NORMAL);
485
486 // print each image (one per line)
487 int row = 1;
488 int remaining_lines = m_height - 1;
489 for (auto& image_stat : m_image_stats) {
490 if (remaining_lines <= 0) {
491 break;
492 }
493 --remaining_lines;
494
495 move(row++, 0);
496 for (auto& pair : m_columns) {
497 str.str("");
498 format_stat(pair.first,
499 image_stat.stats[static_cast<size_t>(pair.first)], str);
500 auto value = str.str().substr(0, STAT_COLUMN_WIDTH);
501
502 str.str("");
503 str << std::right << std::setfill(' ')
504 << std::setw(STAT_COLUMN_WIDTH)
505 << value << ' ';
506 addstr(str.str().c_str());
507 }
508
509 std::string image;
510 if (m_pool_spec.empty()) {
511 image = format_pool_spec(image_stat.pool_name,
512 image_stat.pool_namespace) + "/";
513 }
514 image += image_stat.image_name;
515 addstr(image.substr(0, remaining_cols).c_str());
516 }
517
518 wnoutrefresh(stdscr);
519 }
520
521 void redraw_pending_window() {
522 // draw a "please by patient" window while waiting
523 const char* msg = "Waiting for initial stats";
524 int height = 5;
525 int width = strlen(msg) + 4;;
526 int starty = (m_height - height) / 2;
527 int startx = (m_width - width) / 2;
528
529 if (m_image_stats.empty() && !m_pending_win_opened) {
530 m_pending_win_opened = true;
531 m_pending_win = newwin(height, width, starty, startx);
532 }
533
534 if (m_pending_win != nullptr) {
535 if (m_image_stats.empty()) {
536 box(m_pending_win, 0 , 0);
537 mvwaddstr(m_pending_win, 2, 2, msg);
538 wnoutrefresh(m_pending_win);
539 } else {
540 delwin(m_pending_win);
541 m_pending_win = nullptr;
542 }
543 }
544 }
545
546 void wait_for_key_or_delay() {
547 fd_set fds;
548 FD_ZERO(&fds);
549 FD_SET(STDIN_FILENO, &fds);
550
551 // no point to refreshing faster than the stats period
552 struct timeval tval;
553 tval.tv_sec = std::min<uint32_t>(
554 10, g_conf().get_val<int64_t>("mgr_stats_period"));
555 tval.tv_usec = 0;
556
557 select(STDIN_FILENO + 1, &fds, NULL, NULL, &tval);
558 }
559};
560
561} // namespace iotop
562
563
564void get_arguments_iostat(po::options_description *positional,
565 po::options_description *options) {
566 at::add_pool_options(positional, options, true);
567 options->add_options()
568 ("iterations", po::value<iostat::Iterations>(),
569 "iterations of metric collection [> 0]")
570 ("sort-by", po::value<StatDescriptor>()->default_value(StatDescriptor::WRITE_OPS),
571 "sort-by IO metric "
572 "(write-ops, read-ops, write-bytes, read-bytes, write-latency, read-latency) "
573 "[default: write-ops]");
574 at::add_format_options(options);
575}
576
577int execute_iostat(const po::variables_map &vm,
578 const std::vector<std::string> &ceph_global_init_args) {
579 std::string pool;
580 std::string pool_namespace;
581 size_t arg_index = 0;
582 int r = utils::get_pool_and_namespace_names(vm, false, false, &pool,
583 &pool_namespace, &arg_index);
584 if (r < 0) {
585 return r;
586 }
587
588 uint32_t iterations = 0;
589 if (vm.count("iterations")) {
590 iterations = vm["iterations"].as<uint32_t>();
591 }
592 auto sort_by = vm["sort-by"].as<StatDescriptor>();
593
594 at::Format::Formatter formatter;
595 r = utils::get_formatter(vm, &formatter);
596 if (r < 0) {
597 return r;
598 }
599
600 auto f = formatter.get();
601 if (iterations > 1 && f != nullptr) {
602 std::cerr << "rbd: specifing iterations is not valid with formatted output"
603 << std::endl;
604 return -EINVAL;
605 }
606
607 librados::Rados rados;
608 r = utils::init_rados(&rados);
609 if (r < 0) {
610 return r;
611 }
612
613 r = rados.wait_for_latest_osdmap();
614 if (r < 0) {
615 std::cerr << "rbd: failed to retrieve OSD map" << std::endl;
616 return r;
617 }
618
619 std::string pool_spec = format_pool_spec(pool, pool_namespace);
620
621 // no point to refreshing faster than the stats period
622 auto delay = std::min<uint32_t>(10, g_conf().get_val<int64_t>("mgr_stats_period"));
623
624 ImageStats image_stats;
625 uint32_t count = 0;
626 bool printed_notice = false;
627 while (count++ < iterations || iterations == 0) {
628 r = query_iostats(rados, pool_spec, sort_by, &image_stats, std::cerr);
629 if (r < 0) {
630 return r;
631 }
632
633 if (count == 1 && image_stats.empty()) {
634 count = 0;
635 if (!printed_notice) {
636 std::cerr << "rbd: waiting for initial image stats"
637 << std::endl << std::endl;;
638 printed_notice = true;
639 }
640 } else {
641 iostat::format(image_stats, f, pool_spec.empty());
642 if (f != nullptr) {
643 break;
644 }
645 }
646
647 sleep(delay);
648 }
649
650 return 0;
651}
652
653void get_arguments_iotop(po::options_description *positional,
654 po::options_description *options) {
655 at::add_pool_options(positional, options, true);
656}
657
658int execute_iotop(const po::variables_map &vm,
659 const std::vector<std::string> &ceph_global_init_args) {
660 std::string pool;
661 std::string pool_namespace;
662 size_t arg_index = 0;
663 int r = utils::get_pool_and_namespace_names(vm, false, false, &pool,
664 &pool_namespace, &arg_index);
665 if (r < 0) {
666 return r;
667 }
668
669 librados::Rados rados;
670 r = utils::init_rados(&rados);
671 if (r < 0) {
672 return r;
673 }
674
675 r = rados.wait_for_latest_osdmap();
676 if (r < 0) {
677 std::cerr << "rbd: failed to retrieve OSD map" << std::endl;
678 return r;
679 }
680
681 iotop::MainWindow mainWindow(rados, format_pool_spec(pool, pool_namespace));
682 r = mainWindow.run();
683 if (r < 0) {
684 return r;
685 }
686
687 return 0;
688}
689
690Shell::Action stat_action(
691 {"perf", "image", "iostat"}, {}, "Display image IO statistics.", "",
692 &get_arguments_iostat, &execute_iostat);
693Shell::Action top_action(
694 {"perf", "image", "iotop"}, {}, "Display a top-like IO monitor.", "",
695 &get_arguments_iotop, &execute_iotop);
696
697} // namespace perf
698} // namespace action
699} // namespace rbd