X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=src%2Fceph%2Fsrc%2Fpybind%2Fceph_daemon.py;fp=src%2Fceph%2Fsrc%2Fpybind%2Fceph_daemon.py;h=0000000000000000000000000000000000000000;hb=7da45d65be36d36b880cc55c5036e96c24b53f00;hp=0ab5911955d0143279ad35f55eb7050d404e57c4;hpb=691462d09d0987b47e112d6ee8740375df3c51b2;p=stor4nfv.git diff --git a/src/ceph/src/pybind/ceph_daemon.py b/src/ceph/src/pybind/ceph_daemon.py deleted file mode 100755 index 0ab5911..0000000 --- a/src/ceph/src/pybind/ceph_daemon.py +++ /dev/null @@ -1,407 +0,0 @@ -# -*- mode:python -*- -# vim: ts=4 sw=4 smarttab expandtab - -""" -Copyright (C) 2015 Red Hat - -This is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public -License version 2, as published by the Free Software -Foundation. See file COPYING. -""" - -import sys -import json -import socket -import struct -import time -from collections import OrderedDict -from fcntl import ioctl -from fnmatch import fnmatch -from prettytable import PrettyTable, HEADER -from signal import signal, SIGWINCH -from termios import TIOCGWINSZ - -from ceph_argparse import parse_json_funcsigs, validate_command - -COUNTER = 0x8 -LONG_RUNNING_AVG = 0x4 -READ_CHUNK_SIZE = 4096 - - -def admin_socket(asok_path, cmd, format=''): - """ - Send a daemon (--admin-daemon) command 'cmd'. asok_path is the - path to the admin socket; cmd is a list of strings; format may be - set to one of the formatted forms to get output in that form - (daemon commands don't support 'plain' output). - """ - - def do_sockio(path, cmd_bytes): - """ helper: do all the actual low-level stream I/O """ - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.connect(path) - try: - sock.sendall(cmd_bytes + b'\0') - len_str = sock.recv(4) - if len(len_str) < 4: - raise RuntimeError("no data returned from admin socket") - l, = struct.unpack(">I", len_str) - sock_ret = b'' - - got = 0 - while got < l: - # recv() receives signed int, i.e max 2GB - # workaround by capping READ_CHUNK_SIZE per call. - want = min(l - got, READ_CHUNK_SIZE) - bit = sock.recv(want) - sock_ret += bit - got += len(bit) - - except Exception as sock_e: - raise RuntimeError('exception: ' + str(sock_e)) - return sock_ret - - try: - cmd_json = do_sockio(asok_path, - b'{"prefix": "get_command_descriptions"}') - except Exception as e: - raise RuntimeError('exception getting command descriptions: ' + str(e)) - - if cmd == 'get_command_descriptions': - return cmd_json - - sigdict = parse_json_funcsigs(cmd_json.decode('utf-8'), 'cli') - valid_dict = validate_command(sigdict, cmd) - if not valid_dict: - raise RuntimeError('invalid command') - - if format: - valid_dict['format'] = format - - try: - ret = do_sockio(asok_path, json.dumps(valid_dict).encode('utf-8')) - except Exception as e: - raise RuntimeError('exception: ' + str(e)) - - return ret - - -class Termsize(object): - DEFAULT_SIZE = (25, 80) - def __init__(self): - self.rows, self.cols = self._gettermsize() - self.changed = False - - def _gettermsize(self): - try: - fd = sys.stdin.fileno() - sz = struct.pack('hhhh', 0, 0, 0, 0) - rows, cols = struct.unpack('hhhh', ioctl(fd, TIOCGWINSZ, sz))[:2] - return rows, cols - except IOError: - return self.DEFAULT_SIZE - - def update(self): - rows, cols = self._gettermsize() - if not self.changed: - self.changed = (self.rows, self.cols) != (rows, cols) - self.rows, self.cols = rows, cols - - def reset_changed(self): - self.changed = False - - def __str__(self): - return '%s(%dx%d, changed %s)' % (self.__class__, - self.rows, self.cols, self.changed) - - def __repr__(self): - return 'Termsize(%d,%d,%s)' % (self.__class__, - self.rows, self.cols, self.changed) - - -class DaemonWatcher(object): - """ - Given a Ceph daemon's admin socket path, poll its performance counters - and output a series of output lines showing the momentary values of - counters of interest (those with the 'nick' property in Ceph's schema) - """ - ( - BLACK, - RED, - GREEN, - YELLOW, - BLUE, - MAGENTA, - CYAN, - GRAY - ) = range(8) - - RESET_SEQ = "\033[0m" - COLOR_SEQ = "\033[1;%dm" - COLOR_DARK_SEQ = "\033[0;%dm" - BOLD_SEQ = "\033[1m" - UNDERLINE_SEQ = "\033[4m" - - def __init__(self, asok, statpats=None, min_prio=0): - self.asok_path = asok - self._colored = False - - self._stats = None - self._schema = None - self._statpats = statpats - self._stats_that_fit = dict() - self._min_prio = min_prio - self.termsize = Termsize() - - def supports_color(self, ostr): - """ - Returns True if the running system's terminal supports color, and False - otherwise. - """ - unsupported_platform = (sys.platform in ('win32', 'Pocket PC')) - # isatty is not always implemented, #6223. - is_a_tty = hasattr(ostr, 'isatty') and ostr.isatty() - if unsupported_platform or not is_a_tty: - return False - return True - - def colorize(self, msg, color, dark=False): - """ - Decorate `msg` with escape sequences to give the requested color - """ - return (self.COLOR_DARK_SEQ if dark else self.COLOR_SEQ) % (30 + color) \ - + msg + self.RESET_SEQ - - def bold(self, msg): - """ - Decorate `msg` with escape sequences to make it appear bold - """ - return self.BOLD_SEQ + msg + self.RESET_SEQ - - def format_dimless(self, n, width): - """ - Format a number without units, so as to fit into `width` characters, substituting - an appropriate unit suffix. - """ - units = [' ', 'k', 'M', 'G', 'T', 'P'] - unit = 0 - while len("%s" % (int(n) // (1000**unit))) > width - 1: - unit += 1 - - if unit > 0: - truncated_float = ("%f" % (n / (1000.0 ** unit)))[0:width - 1] - if truncated_float[-1] == '.': - truncated_float = " " + truncated_float[0:-1] - else: - truncated_float = "%{wid}d".format(wid=width-1) % n - formatted = "%s%s" % (truncated_float, units[unit]) - - if self._colored: - if n == 0: - color = self.BLACK, False - else: - color = self.YELLOW, False - return self.bold(self.colorize(formatted[0:-1], color[0], color[1])) \ - + self.bold(self.colorize(formatted[-1], self.BLACK, False)) - else: - return formatted - - def col_width(self, nick): - """ - Given the short name `nick` for a column, how many characters - of width should the column be allocated? Does not include spacing - between columns. - """ - return max(len(nick), 4) - - def get_stats_that_fit(self): - ''' - Get a possibly-truncated list of stats to display based on - current terminal width. Allow breaking mid-section. - ''' - current_fit = OrderedDict() - if self.termsize.changed or not self._stats_that_fit: - width = 0 - for section_name, names in self._stats.items(): - for name, stat_data in names.items(): - width += self.col_width(stat_data) + 1 - if width > self.termsize.cols: - break - if section_name not in current_fit: - current_fit[section_name] = OrderedDict() - current_fit[section_name][name] = stat_data - if width > self.termsize.cols: - break - - self.termsize.reset_changed() - changed = current_fit and (current_fit != self._stats_that_fit) - if changed: - self._stats_that_fit = current_fit - return self._stats_that_fit, changed - - def _print_headers(self, ostr): - """ - Print a header row to `ostr` - """ - header = "" - stats, _ = self.get_stats_that_fit() - for section_name, names in stats.items(): - section_width = \ - sum([self.col_width(x) + 1 for x in names.values()]) - 1 - pad = max(section_width - len(section_name), 0) - pad_prefix = pad // 2 - header += (pad_prefix * '-') - header += (section_name[0:section_width]) - header += ((pad - pad_prefix) * '-') - header += ' ' - header += "\n" - ostr.write(self.colorize(header, self.BLUE, True)) - - sub_header = "" - for section_name, names in stats.items(): - for stat_name, stat_nick in names.items(): - sub_header += self.UNDERLINE_SEQ \ - + self.colorize( - stat_nick.ljust(self.col_width(stat_nick)), - self.BLUE) \ - + ' ' - sub_header = sub_header[0:-1] + self.colorize('|', self.BLUE) - sub_header += "\n" - ostr.write(sub_header) - - def _print_vals(self, ostr, dump, last_dump): - """ - Print a single row of values to `ostr`, based on deltas between `dump` and - `last_dump`. - """ - val_row = "" - fit, changed = self.get_stats_that_fit() - if changed: - self._print_headers(ostr) - for section_name, names in fit.items(): - for stat_name, stat_nick in names.items(): - stat_type = self._schema[section_name][stat_name]['type'] - if bool(stat_type & COUNTER): - n = max(dump[section_name][stat_name] - - last_dump[section_name][stat_name], 0) - elif bool(stat_type & LONG_RUNNING_AVG): - entries = dump[section_name][stat_name]['avgcount'] - \ - last_dump[section_name][stat_name]['avgcount'] - if entries: - n = (dump[section_name][stat_name]['sum'] - - last_dump[section_name][stat_name]['sum']) \ - / float(entries) - n *= 1000.0 # Present in milliseconds - else: - n = 0 - else: - n = dump[section_name][stat_name] - - val_row += self.format_dimless(n, self.col_width(stat_nick)) - val_row += " " - val_row = val_row[0:-1] - val_row += self.colorize("|", self.BLUE) - val_row = val_row[0:-len(self.colorize("|", self.BLUE))] - ostr.write("{0}\n".format(val_row)) - - def _should_include(self, sect, name, prio): - ''' - boolean: should we output this stat? - - 1) If self._statpats exists and the name filename-glob-matches - anything in the list, and prio is high enough, or - 2) If self._statpats doesn't exist and prio is high enough - - then yes. - ''' - if self._statpats: - sectname = '.'.join((sect, name)) - if not any([ - p for p in self._statpats - if fnmatch(name, p) or fnmatch(sectname, p) - ]): - return False - - if self._min_prio is not None and prio is not None: - return (prio >= self._min_prio) - - return True - - def _load_schema(self): - """ - Populate our instance-local copy of the daemon's performance counter - schema, and work out which stats we will display. - """ - self._schema = json.loads( - admin_socket(self.asok_path, ["perf", "schema"]).decode('utf-8'), - object_pairs_hook=OrderedDict) - - # Build list of which stats we will display - self._stats = OrderedDict() - for section_name, section_stats in self._schema.items(): - for name, schema_data in section_stats.items(): - prio = schema_data.get('priority', 0) - if self._should_include(section_name, name, prio): - if section_name not in self._stats: - self._stats[section_name] = OrderedDict() - self._stats[section_name][name] = schema_data['nick'] - if not len(self._stats): - raise RuntimeError("no stats selected by filters") - - def _handle_sigwinch(self, signo, frame): - self.termsize.update() - - def run(self, interval, count=None, ostr=sys.stdout): - """ - Print output at regular intervals until interrupted. - - :param ostr: Stream to which to send output - """ - - self._load_schema() - self._colored = self.supports_color(ostr) - - self._print_headers(ostr) - - last_dump = json.loads(admin_socket(self.asok_path, ["perf", "dump"]).decode('utf-8')) - rows_since_header = 0 - - try: - signal(SIGWINCH, self._handle_sigwinch) - while True: - dump = json.loads(admin_socket(self.asok_path, ["perf", "dump"]).decode('utf-8')) - if rows_since_header >= self.termsize.rows - 2: - self._print_headers(ostr) - rows_since_header = 0 - self._print_vals(ostr, dump, last_dump) - if count is not None: - count -= 1 - if count <= 0: - break - rows_since_header += 1 - last_dump = dump - - # time.sleep() is interrupted by SIGWINCH; avoid that - end = time.time() + interval - while time.time() < end: - time.sleep(end - time.time()) - - except KeyboardInterrupt: - return - - def list(self, ostr=sys.stdout): - """ - Show all selected stats with section, full name, nick, and prio - """ - table = PrettyTable(('section', 'name', 'nick', 'prio')) - table.align['section'] = 'l' - table.align['name'] = 'l' - table.align['nick'] = 'l' - table.align['prio'] = 'r' - self._load_schema() - for section_name, section_stats in self._stats.items(): - for name, nick in section_stats.items(): - prio = self._schema[section_name][name].get('priority') or 0 - table.add_row((section_name, name, nick, prio)) - ostr.write(table.get_string(hrules=HEADER) + '\n')