1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
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"
18 #include <sys/types.h>
21 #include <boost/algorithm/string.hpp>
22 #include <boost/assign.hpp>
23 #include <boost/bimap.hpp>
24 #include <boost/program_options.hpp>
25 #include "json_spirit/json_spirit.h"
31 namespace at
= argument_types
;
32 namespace po
= boost::program_options
;
36 enum class StatDescriptor
{
45 typedef boost::bimap
<StatDescriptor
, std::string
> StatDescriptors
;
47 static const StatDescriptors STAT_DESCRIPTORS
=
48 boost::assign::list_of
<StatDescriptors::relation
>
49 (StatDescriptor::WRITE_OPS
, "write_ops")
50 (StatDescriptor::READ_OPS
, "read_ops")
51 (StatDescriptor::WRITE_BYTES
, "write_bytes")
52 (StatDescriptor::READ_BYTES
, "read_bytes")
53 (StatDescriptor::WRITE_LATENCY
, "write_latency")
54 (StatDescriptor::READ_LATENCY
, "read_latency");
56 std::ostream
& operator<<(std::ostream
& os
, const StatDescriptor
& val
) {
57 auto it
= STAT_DESCRIPTORS
.left
.find(val
);
58 if (it
== STAT_DESCRIPTORS
.left
.end()) {
59 os
<< "unknown (" << static_cast<int>(val
) << ")";
66 void validate(boost::any
& v
, const std::vector
<std::string
>& values
,
67 StatDescriptor
*target_type
, int) {
68 po::validators::check_first_occurrence(v
);
69 std::string s
= po::validators::get_single_string(values
);
70 boost::replace_all(s
, "_", " ");
71 boost::replace_all(s
, "-", "_");
73 auto it
= STAT_DESCRIPTORS
.right
.find(s
);
74 if (it
== STAT_DESCRIPTORS
.right
.end()) {
75 throw po::validation_error(po::validation_error::invalid_option_value
);
77 v
= boost::any(it
->second
);
81 ImageStat(const std::string
& pool_name
, const std::string
& pool_namespace
,
82 const std::string
& image_name
)
83 : pool_name(pool_name
), pool_namespace(pool_namespace
),
84 image_name(image_name
) {
85 stats
.resize(STAT_DESCRIPTORS
.size());
88 std::string pool_name
;
89 std::string pool_namespace
;
90 std::string image_name
;
91 std::vector
<double> stats
;
94 typedef std::vector
<ImageStat
> ImageStats
;
96 typedef std::pair
<std::string
, std::string
> SpecPair
;
98 std::string
format_pool_spec(const std::string
& pool
,
99 const std::string
& pool_namespace
) {
100 std::string pool_spec
{pool
};
101 if (!pool_namespace
.empty()) {
102 pool_spec
+= "/" + pool_namespace
;
107 int query_iostats(librados::Rados
& rados
, const std::string
& pool_spec
,
108 StatDescriptor sort_by
, ImageStats
* image_stats
,
109 std::ostream
& err_os
) {
110 auto sort_by_str
= STAT_DESCRIPTORS
.left
.find(sort_by
)->second
;
112 std::string cmd
= R
"(
114 "prefix
": "rbd perf image stats
",
115 "pool_spec
": ")" + pool_spec + R"(",
116 "sort_by
": ")" + sort_by_str + R"(",
123 int r = rados.mgr_command(cmd, in_bl, &out_bl, &outs);
124 if (r == -EOPNOTSUPP) {
125 err_os << "rbd
: 'rbd_support' mgr module is
not enabled
."
126 << std::endl << std::endl
127 << "Use
'ceph mgr module enable rbd_support' to enable
."
131 err_os << "rbd
: mgr command failed
: " << cpp_strerror(r);
133 err_os << ": " << outs;
139 json_spirit::mValue json_root;
140 if (!json_spirit::read(out_bl.to_str(), json_root)) {
141 err_os << "rbd
: error parsing perf stats
" << std::endl;
145 image_stats->clear();
147 auto& root = json_root.get_obj();
149 // map JSON stat descriptor order to our internal order
150 std::map<uint32_t, uint32_t> json_to_internal_stats;
151 auto& json_stat_descriptors = root["stat_descriptors
"].get_array();
152 for (size_t idx = 0; idx < json_stat_descriptors.size(); ++idx) {
153 auto it = STAT_DESCRIPTORS.right.find(
154 json_stat_descriptors[idx].get_str());
155 if (it == STAT_DESCRIPTORS.right.end()) {
158 json_to_internal_stats[idx] = static_cast<uint32_t>(it->second);
161 // cache a mapping from pool descriptors back to pool-specs
162 std::map<std::string, SpecPair> json_to_internal_pools;
163 auto& pool_descriptors = root["pool_descriptors
"].get_obj();
164 for (auto& pool : pool_descriptors) {
165 auto& pool_spec = pool.second.get_str();
166 auto pos = pool_spec.rfind("/");
168 SpecPair pair{pool_spec.substr(0, pos), ""};
169 if (pos != std::string::npos) {
170 pair.second = pool_spec.substr(pos + 1);
173 json_to_internal_pools[pool.first] = pair;
176 auto& stats = root["stats
"].get_array();
177 for (auto& stat : stats) {
178 auto& stat_obj = stat.get_obj();
179 if (!stat_obj.empty()) {
180 auto& image_spec = stat_obj.begin()->first;
182 auto pos = image_spec.find("/");
183 SpecPair pair{image_spec.substr(0, pos), ""};
184 if (pos != std::string::npos) {
185 pair.second = image_spec.substr(pos + 1);
188 const auto pool_it = json_to_internal_pools.find(pair.first);
189 if (pool_it == json_to_internal_pools.end()) {
193 image_stats->emplace_back(
194 pool_it->second.first, pool_it->second.second, pair.second);
196 auto& image_stat = image_stats->back();
197 auto& data = stat_obj.begin()->second.get_array();
198 for (auto& indexes : json_to_internal_stats) {
199 image_stat.stats[indexes.second] = data[indexes.first].get_real();
203 } catch (std::runtime_error &e) {
204 err_os << "rbd
: error parsing perf stats
: " << e.what() << std::endl;
211 void format_stat(StatDescriptor stat_descriptor, double stat,
213 switch (stat_descriptor) {
214 case StatDescriptor::WRITE_OPS:
215 case StatDescriptor::READ_OPS:
216 os << si_u_t(stat) << "/s
";
218 case StatDescriptor::WRITE_BYTES:
219 case StatDescriptor::READ_BYTES:
220 os << byte_u_t(stat) << "/s
";
222 case StatDescriptor::WRITE_LATENCY:
223 case StatDescriptor::READ_LATENCY:
224 os << std::fixed << std::setprecision(2);
225 if (stat >= 1000000000) {
226 os << (stat / 1000000000) << " s
";
227 } else if (stat >= 1000000) {
228 os << (stat / 1000000) << " ms
";
229 } else if (stat >= 1000) {
230 os << (stat / 1000) << " us
";
241 } // anonymous namespace
245 struct Iterations {};
247 void validate(boost::any& v, const std::vector<std::string>& values,
248 Iterations *target_type, int) {
249 po::validators::check_first_occurrence(v);
250 auto& s = po::validators::get_single_string(values);
253 auto iterations = boost::lexical_cast<uint32_t>(s);
254 if (iterations > 0) {
255 v = boost::any(iterations);
258 } catch (const boost::bad_lexical_cast &) {
260 throw po::validation_error(po::validation_error::invalid_option_value);
263 void format(const ImageStats& image_stats, Formatter* f, bool global_search) {
266 f->open_array_section("images
");
268 tbl.define_column("NAME
", TextTable::LEFT, TextTable::LEFT);
269 for (auto& stat : STAT_DESCRIPTORS.left) {
271 switch (stat.first) {
272 case StatDescriptor::WRITE_OPS:
275 case StatDescriptor::READ_OPS:
278 case StatDescriptor::WRITE_BYTES:
281 case StatDescriptor::READ_BYTES:
284 case StatDescriptor::WRITE_LATENCY:
287 case StatDescriptor::READ_LATENCY:
294 tbl.define_column(title, TextTable::RIGHT, TextTable::RIGHT);
298 for (auto& image_stat : image_stats) {
300 f->open_object_section("image
");
301 f->dump_string("pool
", image_stat.pool_name);
302 f->dump_string("pool_namespace
", image_stat.pool_namespace);
303 f->dump_string("image
", image_stat.image_name);
304 for (auto& pair : STAT_DESCRIPTORS.left) {
305 f->dump_float(pair.second.c_str(),
306 image_stat.stats[static_cast<size_t>(pair.first)]);
312 name += image_stat.pool_name + "/";
313 if (!image_stat.pool_namespace.empty()) {
314 name += image_stat.pool_namespace + "/";
317 name += image_stat.image_name;
320 for (auto& pair : STAT_DESCRIPTORS.left) {
321 std::stringstream str;
322 format_stat(pair.first,
323 image_stat.stats[static_cast<size_t>(pair.first)], str);
327 tbl << TextTable::endrow;
335 std::cout << tbl << std::endl;
339 } // namespace iostat
345 MainWindow(librados::Rados& rados, const std::string& pool_spec)
346 : m_rados(rados), m_pool_spec(pool_spec) {
351 keypad(stdscr, TRUE);
352 nodelay(stdscr, TRUE);
361 std::stringstream err_str;
363 r = query_iostats(m_rados, m_pool_spec, m_sort_by, &m_image_stats,
371 wait_for_key_or_delay();
374 if (ch == 'q' || ch == 'Q') {
376 } else if (ch == '<' || ch == KEY_LEFT) {
377 auto it = STAT_DESCRIPTORS.left.find(m_sort_by);
378 if (it != STAT_DESCRIPTORS.left.begin()) {
379 m_sort_by = (--it)->first;
381 } else if (ch == '>' || ch == KEY_RIGHT) {
382 auto it = STAT_DESCRIPTORS.left.find(m_sort_by);
383 if (it != STAT_DESCRIPTORS.left.end() &&
384 ++it != STAT_DESCRIPTORS.left.end()) {
385 m_sort_by = it->first;
393 std::cerr << err_str.str() << std::endl;
399 static const size_t STAT_COLUMN_WIDTH = 12;
401 librados::Rados& m_rados;
402 std::string m_pool_spec;
404 ImageStats m_image_stats;
405 StatDescriptor m_sort_by = StatDescriptor::WRITE_OPS;
407 bool m_pending_win_opened = false;
408 WINDOW* m_pending_win = nullptr;
413 std::map<StatDescriptor, std::string> m_columns;
415 void init_columns() {
417 for (auto& pair : STAT_DESCRIPTORS.left) {
419 switch (pair.first) {
420 case StatDescriptor::WRITE_OPS:
421 title = "WRITES OPS
";
423 case StatDescriptor::READ_OPS:
426 case StatDescriptor::WRITE_BYTES:
427 title = "WRITE BYTES
";
429 case StatDescriptor::READ_BYTES:
430 title = "READ BYTES
";
432 case StatDescriptor::WRITE_LATENCY:
435 case StatDescriptor::READ_LATENCY:
442 m_columns[pair.first] = (title);
447 getmaxyx(stdscr, m_height, m_width);
449 redraw_main_window();
450 redraw_pending_window();
455 void redraw_main_window() {
457 mvhline(0, 0, ' ' | A_REVERSE, m_width);
459 // print header for all metrics
460 int remaining_cols = m_width;
461 std::stringstream str;
462 for (auto& pair : m_columns) {
463 int attr = A_REVERSE;
465 if (pair.first == m_sort_by) {
471 title += pair.second;
474 str << std::right << std::setfill(' ')
475 << std::setw(STAT_COLUMN_WIDTH)
479 addstr(str.str().c_str());
480 remaining_cols -= title.size();
487 // print each image (one per line)
489 int remaining_lines = m_height - 1;
490 for (auto& image_stat : m_image_stats) {
491 if (remaining_lines <= 0) {
497 for (auto& pair : m_columns) {
499 format_stat(pair.first,
500 image_stat.stats[static_cast<size_t>(pair.first)], str);
501 auto value = str.str().substr(0, STAT_COLUMN_WIDTH);
504 str << std::right << std::setfill(' ')
505 << std::setw(STAT_COLUMN_WIDTH)
507 addstr(str.str().c_str());
511 if (m_pool_spec.empty()) {
512 image = format_pool_spec(image_stat.pool_name,
513 image_stat.pool_namespace) + "/";
515 image += image_stat.image_name;
516 addstr(image.substr(0, remaining_cols).c_str());
519 wnoutrefresh(stdscr);
522 void redraw_pending_window() {
523 // draw a "please by patient
" window while waiting
524 const char* msg = "Waiting
for initial stats
";
526 int width = strlen(msg) + 4;;
527 int starty = (m_height - height) / 2;
528 int startx = (m_width - width) / 2;
530 if (m_image_stats.empty() && !m_pending_win_opened) {
531 m_pending_win_opened = true;
532 m_pending_win = newwin(height, width, starty, startx);
535 if (m_pending_win != nullptr) {
536 if (m_image_stats.empty()) {
537 box(m_pending_win, 0 , 0);
538 mvwaddstr(m_pending_win, 2, 2, msg);
539 wnoutrefresh(m_pending_win);
541 delwin(m_pending_win);
542 m_pending_win = nullptr;
547 void wait_for_key_or_delay() {
550 FD_SET(STDIN_FILENO, &fds);
552 // no point to refreshing faster than the stats period
554 tval.tv_sec = std::min<uint32_t>(
555 10, g_conf().get_val<int64_t>("mgr_stats_period
"));
558 select(STDIN_FILENO + 1, &fds, NULL, NULL, &tval);
565 void get_arguments_iostat(po::options_description *positional,
566 po::options_description *options) {
567 at::add_pool_options(positional, options, true);
568 options->add_options()
569 ("iterations
", po::value<iostat::Iterations>(),
570 "iterations of metric collection
[> 0]")
571 ("sort
-by
", po::value<StatDescriptor>()->default_value(StatDescriptor::WRITE_OPS),
573 "(write
-ops
, read
-ops
, write
-bytes
, read
-bytes
, write
-latency
, read
-latency
) "
574 "[default: write
-ops
]");
575 at::add_format_options(options);
578 int execute_iostat(const po::variables_map &vm,
579 const std::vector<std::string> &ceph_global_init_args) {
581 std::string pool_namespace;
582 size_t arg_index = 0;
583 int r = utils::get_pool_and_namespace_names(vm, false, false, &pool,
584 &pool_namespace, &arg_index);
589 uint32_t iterations = 0;
590 if (vm.count("iterations
")) {
591 iterations = vm["iterations
"].as<uint32_t>();
593 auto sort_by = vm["sort
-by
"].as<StatDescriptor>();
595 at::Format::Formatter formatter;
596 r = utils::get_formatter(vm, &formatter);
601 auto f = formatter.get();
602 if (iterations > 1 && f != nullptr) {
603 std::cerr << "rbd
: specifing iterations is
not valid with formatted output
"
608 librados::Rados rados;
609 r = utils::init_rados(&rados);
614 r = rados.wait_for_latest_osdmap();
616 std::cerr << "rbd
: failed to retrieve OSD map
" << std::endl;
620 std::string pool_spec = format_pool_spec(pool, pool_namespace);
622 // no point to refreshing faster than the stats period
623 auto delay = std::min<uint32_t>(10, g_conf().get_val<int64_t>("mgr_stats_period
"));
625 ImageStats image_stats;
627 bool printed_notice = false;
628 while (count++ < iterations || iterations == 0) {
629 r = query_iostats(rados, pool_spec, sort_by, &image_stats, std::cerr);
634 if (count == 1 && image_stats.empty()) {
636 if (!printed_notice) {
637 std::cerr << "rbd
: waiting
for initial image stats
"
638 << std::endl << std::endl;;
639 printed_notice = true;
642 iostat::format(image_stats, f, pool_spec.empty());
654 void get_arguments_iotop(po::options_description *positional,
655 po::options_description *options) {
656 at::add_pool_options(positional, options, true);
659 int execute_iotop(const po::variables_map &vm,
660 const std::vector<std::string> &ceph_global_init_args) {
662 std::string pool_namespace;
663 size_t arg_index = 0;
664 int r = utils::get_pool_and_namespace_names(vm, false, false, &pool,
665 &pool_namespace, &arg_index);
670 librados::Rados rados;
671 r = utils::init_rados(&rados);
676 r = rados.wait_for_latest_osdmap();
678 std::cerr << "rbd
: failed to retrieve OSD map
" << std::endl;
682 iotop::MainWindow mainWindow(rados, format_pool_spec(pool, pool_namespace));
683 r = mainWindow.run();
691 Shell::Action stat_action(
692 {"perf
", "image
", "iostat
"}, {}, "Display image IO statistics
.", "",
693 &get_arguments_iostat, &execute_iostat);
694 Shell::Action top_action(
695 {"perf
", "image
", "iotop
"}, {}, "Display a top
-like IO monitor
.", "",
696 &get_arguments_iotop, &execute_iotop);
699 } // namespace action