From 05d37379f39e11ee75793a17985d57a34127ae78 Mon Sep 17 00:00:00 2001 From: Mohamad Gebai Date: Tue, 10 Apr 2018 16:37:39 -0400 Subject: [PATCH] mgr/iostat: print output as a table Signed-off-by: Mohamad Gebai --- src/ceph.in | 12 ++++++-- src/pybind/mgr/iostat/module.py | 33 ++++++++++++++++++-- src/pybind/mgr/mgr_module.py | 54 ++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 7 deletions(-) diff --git a/src/ceph.in b/src/ceph.in index b4a75b09ff9d9..82ee08e39381f 100755 --- a/src/ceph.in +++ b/src/ceph.in @@ -533,16 +533,22 @@ def do_command(parsed_args, target, cmdargs, sigdict, inbuf, verbose): else: return -errno.EINVAL, '', 'invalid command' + next_header_print = 0 + # Set extra options for polling commands only: + if valid_dict.get('poll', False): + valid_dict['width'] = Termsize().cols while True: try: - if next_header_print == 0: + # Only print the header for polling commands + if next_header_print == 0 and valid_dict.get('poll', False): valid_dict['print_header'] = True next_header_print = Termsize().rows - 3 next_header_print -= 1 ret, outbuf, outs = json_command(cluster_handle, target=target, argdict=valid_dict, inbuf=inbuf) - valid_dict['print_header'] = False - if 'poll' not in valid_dict or not valid_dict['poll']: + if valid_dict.get('poll', False): + valid_dict['print_header'] = False + if not valid_dict.get('poll', False): # Don't print here if it's not a polling command break if ret: diff --git a/src/pybind/mgr/iostat/module.py b/src/pybind/mgr/iostat/module.py index e42925c8bf8c0..e9f7c3eb92b95 100644 --- a/src/pybind/mgr/iostat/module.py +++ b/src/pybind/mgr/iostat/module.py @@ -25,7 +25,10 @@ class Module(MgrModule): def handle_command(self, command): rd = 0 wr = 0 - ops = 0 + total = 0 + rd_ops = 0 + wr_ops = 0 + total_ops = 0 ret = '' if command['prefix'] == 'iostat': @@ -35,9 +38,33 @@ class Module(MgrModule): if (stamp_delta > 0): rd = int(r['pg_stats_delta']['stat_sum']['num_read_kb']) / stamp_delta wr = int(r['pg_stats_delta']['stat_sum']['num_write_kb']) / stamp_delta - ops = ( int(r['pg_stats_delta']['stat_sum']['num_write']) + int(r['pg_stats_delta']['stat_sum']['num_read']) ) / stamp_delta + # The values are in kB, but to_pretty_iec() requires them to be in bytes + rd = int(rd) << 10 + wr = int(wr) << 10 + total = rd + wr - ret = "wr: {0} kB/s, rd: {1} kB/s, iops: {2}".format(int(wr), int(rd), int(ops)) + rd_ops = int(r['pg_stats_delta']['stat_sum']['num_read']) / stamp_delta + wr_ops = int(r['pg_stats_delta']['stat_sum']['num_write']) / stamp_delta + total_ops = rd_ops + wr_ops + + if 'width' in command: + width = command['width'] + else: + width = 80 + + if command.get('print_header', False): + elems = ['Read', 'Write', 'Total', 'Read IOPS', 'Write IOPS', 'Total IOPS'] + ret += self.get_pretty_header(elems, width) + + elems = [ + self.to_pretty_iec(rd) + 'B/s', + self.to_pretty_iec(wr) + 'B/s', + self.to_pretty_iec(total) + 'B/s', + int(rd_ops), + int(wr_ops), + int(total_ops) + ] + ret += self.get_pretty_row(elems, width) elif command['prefix'] == 'iostat self-test': r = self.get('io_rate') diff --git a/src/pybind/mgr/mgr_module.py b/src/pybind/mgr/mgr_module.py index b51952ebd18e0..84e7826a3a24c 100644 --- a/src/pybind/mgr/mgr_module.py +++ b/src/pybind/mgr/mgr_module.py @@ -342,7 +342,59 @@ class MgrModule(ceph_module.BaseMgrModule): return "/s" elif unit == self.BYTES: return "B/s" - + + def to_pretty_iec(self, n): + for bits, suffix in [(60, 'Ei'), (50, 'Pi'), (40, 'Ti'), (30, 'Gi'), + (20, 'Mi'), (10, 'Ki')]: + if n > 10 << bits: + return str(n >> bits) + ' ' + suffix + return str(n) + ' ' + + def get_pretty_row(self, elems, width): + """ + Takes an array of elements and returns a string with those elements + formatted as a table row. Useful for polling modules. + + :param elems: the elements to be printed + :param width: the width of the terminal + """ + n = len(elems) + column_width = width / n + + ret = '|' + for elem in elems: + ret += '{0:>{w}} |'.format(elem, w=column_width - 2) + + return ret + + def get_pretty_header(self, elems, width): + """ + Like ``get_pretty_row`` but adds dashes, to be used as a table title. + + :param elems: the elements to be printed + :param width: the width of the terminal + """ + n = len(elems) + column_width = width / n + + # dash line + ret = '+' + for i in range(0, n): + ret += '-' * (column_width - 1) + '+' + ret += '\n' + + # title + ret += self.get_pretty_row(elems, width) + ret += '\n' + + # dash line + ret += '+' + for i in range(0, n): + ret += '-' * (column_width - 1) + '+' + ret += '\n' + + return ret + def get_server(self, hostname): """ Called by the plugin to fetch metadata about a particular hostname from -- 2.39.5