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