From 58b055db94eee5688ff126aebf1e5ad4c1a57aeb Mon Sep 17 00:00:00 2001 From: Sebastian Wagner Date: Fri, 23 Mar 2018 10:29:27 +0100 Subject: [PATCH] mgr/dashboard: Refactor multiple duplicates of `get_rate()` ... And `get_latest()`. * OSD Controller: `.stats_history` now returns the derivative. Fixes https://tracker.ceph.com/issues/23389 Signed-off-by: Sebastian Wagner --- .../mgr/dashboard/controllers/cephfs.py | 52 ++++++------------- src/pybind/mgr/dashboard/controllers/osd.py | 24 ++------- .../dashboard/controllers/perf_counters.py | 17 ++---- .../mgr/dashboard/controllers/tcmu_iscsi.py | 21 ++------ .../mgr/dashboard/services/ceph_service.py | 44 +++++++++++++++- src/pybind/mgr/dashboard/tox.ini | 2 +- src/pybind/mgr/mgr_module.py | 20 +++---- 7 files changed, 80 insertions(+), 100 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/cephfs.py b/src/pybind/mgr/dashboard/controllers/cephfs.py index f375a950da4a8..ddb41cd10bbd3 100644 --- a/src/pybind/mgr/dashboard/controllers/cephfs.py +++ b/src/pybind/mgr/dashboard/controllers/cephfs.py @@ -5,10 +5,9 @@ from collections import defaultdict import cherrypy -from ..services.ceph_service import CephService - from . import ApiController, AuthRequired, BaseController from .. import mgr +from ..services.ceph_service import CephService from ..tools import ViewCache @@ -98,14 +97,6 @@ class CephFS(BaseController): return names - def get_rate(self, daemon_type, daemon_name, stat): - data = mgr.get_counter(daemon_type, daemon_name, stat)[stat] - - if data and len(data) > 1: - return (data[-1][1] - data[-2][1]) / float(data[-1][0] - data[-2][0]) - - return 0 - # pylint: disable=too-many-locals,too-many-statements,too-many-branches def fs_status(self, fs_id): mds_versions = defaultdict(list) @@ -132,17 +123,17 @@ class CephFS(BaseController): if up: gid = mdsmap['up']["mds_{0}".format(rank)] info = mdsmap['info']['gid_{0}'.format(gid)] - dns = self.get_latest("mds", info['name'], "mds.inodes") - inos = self.get_latest("mds", info['name'], "mds_mem.ino") + dns = mgr.get_latest("mds", info['name'], "mds.inodes") + inos = mgr.get_latest("mds", info['name'], "mds_mem.ino") if rank == 0: - client_count = self.get_latest("mds", info['name'], - "mds_sessions.session_count") + client_count = mgr.get_latest("mds", info['name'], + "mds_sessions.session_count") elif client_count == 0: # In case rank 0 was down, look at another rank's # sessionmap to get an indication of clients. - client_count = self.get_latest("mds", info['name'], - "mds_sessions.session_count") + client_count = mgr.get_latest("mds", info['name'], + "mds_sessions.session_count") laggy = "laggy_since" in info @@ -150,20 +141,15 @@ class CephFS(BaseController): if laggy: state += "(laggy)" - # if state == "active" and not laggy: - # c_state = self.colorize(state, self.GREEN) - # else: - # c_state = self.colorize(state, self.YELLOW) - # Populate based on context of state, e.g. client # ops for an active daemon, replay progress, reconnect # progress - activity = "" - if state == "active": - activity = self.get_rate("mds", - info['name'], - "mds_server.handle_client_request") + activity = CephService.get_rate("mds", + info['name'], + "mds_server.handle_client_request") + else: + activity = 0.0 metadata = mgr.get_metadata('mds', info['name']) mds_versions[metadata.get('ceph_version', 'unknown')].append( @@ -185,7 +171,7 @@ class CephFS(BaseController): "rank": rank, "state": "failed", "mds": "", - "activity": "", + "activity": 0.0, "dns": 0, "inos": 0 } @@ -197,10 +183,10 @@ class CephFS(BaseController): if daemon_info['state'] != "up:standby-replay": continue - inos = self.get_latest("mds", daemon_info['name'], "mds_mem.ino") - dns = self.get_latest("mds", daemon_info['name'], "mds.inodes") + inos = mgr.get_latest("mds", daemon_info['name'], "mds_mem.ino") + dns = mgr.get_latest("mds", daemon_info['name'], "mds.inodes") - activity = self.get_rate( + activity = CephService.get_rate( "mds", daemon_info['name'], "mds_log.replay") rank_table.append( @@ -292,12 +278,6 @@ class CephFS(BaseController): 'data': clients } - def get_latest(self, daemon_type, daemon_name, stat): - data = mgr.get_counter(daemon_type, daemon_name, stat)[stat] - if data: - return data[-1][1] - return 0 - class CephFSClients(object): def __init__(self, module_inst, fscid): diff --git a/src/pybind/mgr/dashboard/controllers/osd.py b/src/pybind/mgr/dashboard/controllers/osd.py index f4a4e68c22a9f..4844ee9857e1d 100644 --- a/src/pybind/mgr/dashboard/controllers/osd.py +++ b/src/pybind/mgr/dashboard/controllers/osd.py @@ -7,28 +7,12 @@ from mgr_module import CommandResult from . import ApiController, AuthRequired, RESTController from .. import logger, mgr +from ..services.ceph_service import CephService @ApiController('osd') @AuthRequired() class Osd(RESTController): - def get_counter(self, daemon_name, stat): - return mgr.get_counter('osd', daemon_name, stat)[stat] - - def get_rate(self, daemon_name, stat): - data = self.get_counter(daemon_name, stat) - rate = 0 - if data and len(data) > 1: - rate = (data[-1][1] - data[-2][1]) / float(data[-1][0] - data[-2][0]) - return rate - - def get_latest(self, daemon_name, stat): - data = self.get_counter(daemon_name, stat) - latest = 0 - if data and data[-1] and len(data[-1]) == 2: - latest = data[-1][1] - return latest - def list(self): osds = self.get_osd_map() # Extending by osd stats information @@ -53,11 +37,11 @@ class Osd(RESTController): osd_spec = str(o['osd']) for s in ['osd.op_w', 'osd.op_in_bytes', 'osd.op_r', 'osd.op_out_bytes']: prop = s.split('.')[1] - o['stats'][prop] = self.get_rate(osd_spec, s) - o['stats_history'][prop] = self.get_counter(osd_spec, s) + o['stats'][prop] = CephService.get_rate('osd', osd_spec, s) + o['stats_history'][prop] = CephService.get_rates('osd', osd_spec, s) # Gauge stats for s in ['osd.numpg', 'osd.stat_bytes', 'osd.stat_bytes_used']: - o['stats'][s.split('.')[1]] = self.get_latest(osd_spec, s) + o['stats'][s.split('.')[1]] = mgr.get_latest('osd', osd_spec, s) return list(osds.values()) def get_osd_map(self): diff --git a/src/pybind/mgr/dashboard/controllers/perf_counters.py b/src/pybind/mgr/dashboard/controllers/perf_counters.py index af0631a0a6659..cced79f1883ac 100644 --- a/src/pybind/mgr/dashboard/controllers/perf_counters.py +++ b/src/pybind/mgr/dashboard/controllers/perf_counters.py @@ -3,23 +3,12 @@ from __future__ import absolute_import from . import ApiController, AuthRequired, RESTController from .. import mgr +from ..services.ceph_service import CephService class PerfCounter(RESTController): service_type = None # type: str - def _get_rate(self, daemon_type, daemon_name, stat): - data = mgr.get_counter(daemon_type, daemon_name, stat)[stat] - if data and len(data) > 1: - return (data[-1][1] - data[-2][1]) / float(data[-1][0] - data[-2][0]) - return 0 - - def _get_latest(self, daemon_type, daemon_name, stat): - data = mgr.get_counter(daemon_type, daemon_name, stat)[stat] - if data: - return data[-1][1] - return 0 - def get(self, service_id): schema_dict = mgr.get_perf_schema(self.service_type, str(service_id)) schema = schema_dict["{}.{}".format(self.service_type, service_id)] @@ -31,11 +20,11 @@ class PerfCounter(RESTController): counter['description'] = value['description'] # pylint: disable=W0212 if mgr._stattype_to_str(value['type']) == 'counter': - counter['value'] = self._get_rate( + counter['value'] = CephService.get_rate( self.service_type, service_id, key) counter['unit'] = mgr._unit_to_str(value['units']) else: - counter['value'] = self._get_latest( + counter['value'] = mgr.get_latest( self.service_type, service_id, key) counter['unit'] = '' counters.append(counter) diff --git a/src/pybind/mgr/dashboard/controllers/tcmu_iscsi.py b/src/pybind/mgr/dashboard/controllers/tcmu_iscsi.py index 2b32a9a69aa1d..098e4b0d7c49e 100644 --- a/src/pybind/mgr/dashboard/controllers/tcmu_iscsi.py +++ b/src/pybind/mgr/dashboard/controllers/tcmu_iscsi.py @@ -11,21 +11,6 @@ SERVICE_TYPE = 'tcmu-runner' @ApiController('tcmuiscsi') @AuthRequired() class TcmuIscsi(RESTController): - def _get_rate(self, daemon_type, daemon_name, stat): - data = mgr.get_counter(daemon_type, daemon_name, stat)[stat] - - if data and len(data) > 1: - last_value = data[0][1] - last_time = data[0][0] - rates = [] - for datum in data[1:]: - rates.append([datum[0], ((datum[1] - last_value) / - float(datum[0] - last_time))]) - last_value = datum[1] - last_time = datum[0] - return rates - return [[0, 0]] - # pylint: disable=too-many-locals,too-many-nested-blocks def list(self): # pylint: disable=unused-argument daemons = {} @@ -78,9 +63,9 @@ class TcmuIscsi(RESTController): image['stats_history'] = {} for s in ['rd', 'wr', 'rd_bytes', 'wr_bytes']: perf_key = "{}{}".format(perf_key_prefix, s) - image['stats'][s] = self._get_rate( - 'tcmu-runner', service_id, perf_key)[-1][1] - image['stats_history'][s] = self._get_rate( + image['stats'][s] = CephService.get_rate( + 'tcmu-runner', service_id, perf_key) + image['stats_history'][s] = CephService.get_rates( 'tcmu-runner', service_id, perf_key) else: daemon['non_optimized_paths'] += 1 diff --git a/src/pybind/mgr/dashboard/services/ceph_service.py b/src/pybind/mgr/dashboard/services/ceph_service.py index 9f113cf907d57..7c1d9928a3073 100644 --- a/src/pybind/mgr/dashboard/services/ceph_service.py +++ b/src/pybind/mgr/dashboard/services/ceph_service.py @@ -7,6 +7,16 @@ from collections import defaultdict import json from mgr_module import CommandResult + +try: + from more_itertools import pairwise +except ImportError: + def pairwise(iterable): + from itertools import tee + a, b = tee(iterable) + next(b, None) + return zip(a, b) + from .. import logger, mgr @@ -90,8 +100,7 @@ class CephService(object): def get_rate(series): if len(series) >= 2: - return (float(series[0][1]) - float(series[1][1])) / \ - (float(series[0][0]) - float(series[1][0])) + return differentiate(*series[0:1]) return 0 for stat_name, stat_series in stats.items(): @@ -142,3 +151,34 @@ class CephService(object): return json.loads(outb) except Exception: # pylint: disable=broad-except return outb + + @classmethod + def get_rates(cls, svc_type, svc_name, path): + """ + :return: the derivative of mgr.get_counter() + :rtype: list[tuple[int, float]]""" + data = mgr.get_counter(svc_type, svc_name, path)[path] + if not data: + return [(0, 0)] + elif len(data) == 1: + return [(data[0][0], 0)] + return [(data2[0], differentiate(data1, data2)) for data1, data2 in pairwise(data)] + + @classmethod + def get_rate(cls, svc_type, svc_name, path): + """returns most recent rate""" + data = mgr.get_counter(svc_type, svc_name, path)[path] + + if data and len(data) > 1: + return differentiate(*data[-2:]) + return 0.0 + + +def differentiate(data1, data2): + """ + >>> times = [0, 2] + >>> values = [100, 101] + >>> differentiate(*zip(times, values)) + 0.5 + """ + return (data2[1] - data1[1]) / float(data2[0] - data1[0]) diff --git a/src/pybind/mgr/dashboard/tox.ini b/src/pybind/mgr/dashboard/tox.ini index 743a8a6929e5b..b9170256efe35 100644 --- a/src/pybind/mgr/dashboard/tox.ini +++ b/src/pybind/mgr/dashboard/tox.ini @@ -12,7 +12,7 @@ setenv= LD_LIBRARY_PATH = {toxinidir}/../../../../build/lib PATH = {toxinidir}/../../../../build/bin:$PATH commands= - {envbindir}/py.test --cov=. --cov-report= --junitxml=junit.{envname}.xml --doctest-modules controllers/rbd.py tests/ + {envbindir}/py.test --cov=. --cov-report= --junitxml=junit.{envname}.xml --doctest-modules controllers/rbd.py services/ tests/ [testenv:cov-init] setenv = diff --git a/src/pybind/mgr/mgr_module.py b/src/pybind/mgr/mgr_module.py index ce68b459d823e..480fc8586d0f7 100644 --- a/src/pybind/mgr/mgr_module.py +++ b/src/pybind/mgr/mgr_module.py @@ -578,6 +578,15 @@ class MgrModule(ceph_module.BaseMgrModule): """ return self._ceph_get_osdmap() + # TODO: improve C++->Python interface to return just + # the latest if that's all we want. + def get_latest(self, daemon_type, daemon_name, counter): + data = self.get_counter(daemon_type, daemon_name, counter)[counter] + if data: + return data[-1][1] + else: + return 0 + def get_all_perf_counters(self, prio_limit=PRIO_USEFUL): """ Return the perf counters currently known to this ceph-mgr @@ -593,14 +602,6 @@ class MgrModule(ceph_module.BaseMgrModule): result = defaultdict(dict) - # TODO: improve C++->Python interface to return just - # the latest if that's all we want. - def get_latest(daemon_type, daemon_name, counter): - data = self.get_counter(daemon_type, daemon_name, counter)[counter] - if data: - return data[-1][1] - else: - return 0 for server in self.list_servers(): for service in server['services']: @@ -628,7 +629,8 @@ class MgrModule(ceph_module.BaseMgrModule): continue counter_info = counter_schema - counter_info['value'] = get_latest(service['type'], service['id'], counter_path) + counter_info['value'] = self.get_latest(service['type'], service['id'], + counter_path) result[svc_full_name][counter_path] = counter_info self.log.debug("returning {0} counter".format(len(result))) -- 2.39.5