1
0
mirror of https://github.com/ceph/ceph synced 2025-04-01 23:02:17 +00:00

Merge pull request from sebastian-philipp/dashboard_get_range

mgr/dashboard: Refactor multiple duplicates of `get_rate()`

Reviewed-by: Ricardo Dias <rdias@suse.com>
Reviewed-by: Stephan Müller <smueller@suse.com>
This commit is contained in:
Lenz Grimmer 2018-04-11 12:22:00 +02:00 committed by GitHub
commit 34232b347b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 164 additions and 104 deletions

View File

@ -7,8 +7,11 @@ import logging
import os
import subprocess
import sys
from collections import namedtuple
import requests
import six
from ..mgr_test_case import MgrTestCase
@ -117,6 +120,12 @@ class DashboardTestCase(MgrTestCase):
body = self._resp.json()
self.assertEqual(body, data)
def assertSchema(self, data, schema):
try:
return _validate_json(data, schema)
except _ValError as e:
self.assertEqual(data, str(e))
def assertBody(self, body):
self.assertEqual(self._resp.text, body)
@ -148,3 +157,74 @@ class DashboardTestCase(MgrTestCase):
out = cls.ceph_cluster.mon_manager.raw_cluster_cmd('mon_status')
j = json.loads(out)
return [mon['name'] for mon in j['monmap']['mons']]
class JLeaf(namedtuple('JLeaf', ['typ', 'none'])):
def __new__(cls, typ, none=False):
if typ == str:
typ = six.string_types
return super(JLeaf, cls).__new__(cls, typ, none)
JList = namedtuple('JList', ['elem_typ'])
JTuple = namedtuple('JList', ['elem_typs'])
class JObj(namedtuple('JObj', ['sub_elems', 'allow_unknown'])):
def __new__(cls, sub_elems, allow_unknown=False):
"""
:type sub_elems: dict[str, JAny | JLeaf | JList | JObj]
:type allow_unknown: bool
:return:
"""
return super(JObj, cls).__new__(cls, sub_elems, allow_unknown)
JAny = namedtuple('JAny', ['none'])
class _ValError(Exception):
def __init__(self, msg, path):
path_str = ''.join('[{}]'.format(repr(p)) for p in path)
super(_ValError, self).__init__('In `input{}`: {}'.format(path_str, msg))
def _validate_json(val, schema, path=[]):
"""
>>> d = {'a': 1, 'b': 'x', 'c': range(10)}
... ds = JObj({'a': JLeaf(int), 'b': JLeaf(str), 'c': JList(JLeaf(int))})
... _validate_json(d, ds)
True
"""
if isinstance(schema, JAny):
if not schema.none and val is None:
raise _ValError('val is None', path)
return True
if isinstance(schema, JLeaf):
if schema.none and val is None:
return True
if not isinstance(val, schema.typ):
raise _ValError('val not of type {}'.format(schema.typ), path)
return True
if isinstance(schema, JList):
return all(_validate_json(e, schema.elem_typ, path + [i]) for i, e in enumerate(val))
if isinstance(schema, JTuple):
return all(_validate_json(val[i], typ, path + [i])
for i, typ in enumerate(schema.elem_typs))
if isinstance(schema, JObj):
missing_keys = set(schema.sub_elems.keys()).difference(set(val.keys()))
if missing_keys:
raise _ValError('missing keys: {}'.format(missing_keys), path)
unknown_keys = set(val.keys()).difference(set(schema.sub_elems.keys()))
if not schema.allow_unknown and unknown_keys:
raise _ValError('unknown keys: {}'.format(unknown_keys), path)
return all(
_validate_json(val[sub_elem_name], sub_elem, path + [sub_elem_name])
for sub_elem_name, sub_elem in schema.sub_elems.items()
)
assert False, str(path)

View File

@ -2,15 +2,13 @@
from __future__ import absolute_import
from .helper import DashboardTestCase, authenticate
from .helper import DashboardTestCase, authenticate, JObj, JAny, JList, JLeaf, JTuple
class OsdTest(DashboardTestCase):
def assert_in_and_not_none(self, data, properties):
for prop in properties:
self.assertIn(prop, data)
self.assertIsNotNone(data[prop])
self.assertSchema(data, JObj({p: JAny(none=False) for p in properties}, allow_unknown=True))
@authenticate
def test_list(self):
@ -25,6 +23,8 @@ class OsdTest(DashboardTestCase):
self.assert_in_and_not_none(data['stats'], ['numpg', 'stat_bytes_used', 'stat_bytes',
'op_r', 'op_w'])
self.assert_in_and_not_none(data['stats_history'], ['op_out_bytes', 'op_in_bytes'])
self.assertSchema(data['stats_history']['op_out_bytes'],
JList(JTuple([JLeaf(int), JLeaf(float)])))
@authenticate
def test_details(self):

View File

@ -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):

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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])

View File

@ -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 =

View File

@ -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)))