From 018b386f0c00a99652454f99d6b42a3a41fb591d Mon Sep 17 00:00:00 2001 From: John Spray Date: Sat, 15 Jul 2017 19:10:37 -0400 Subject: [PATCH] mgr/dashboard: add OSD list view Signed-off-by: John Spray --- src/pybind/mgr/dashboard/base.html | 18 +- src/pybind/mgr/dashboard/module.py | 225 +++++++++++++++++-------- src/pybind/mgr/dashboard/osd_perf.html | 4 +- src/pybind/mgr/dashboard/osds.html | 105 ++++++++++++ 4 files changed, 279 insertions(+), 73 deletions(-) create mode 100644 src/pybind/mgr/dashboard/osds.html diff --git a/src/pybind/mgr/dashboard/base.html b/src/pybind/mgr/dashboard/base.html index efe09260e30..6a67410e0de 100644 --- a/src/pybind/mgr/dashboard/base.html +++ b/src/pybind/mgr/dashboard/base.html @@ -20,6 +20,7 @@ href="/static/AdminLTE-2.3.7/dist/css/skins/skin-blue.min.css"> + @@ -259,9 +260,20 @@ Cluster health -
  • - - Servers +
  • + Cluster + + + + +
  • Block diff --git a/src/pybind/mgr/dashboard/module.py b/src/pybind/mgr/dashboard/module.py index d3ce972811a..42f62a8c8af 100644 --- a/src/pybind/mgr/dashboard/module.py +++ b/src/pybind/mgr/dashboard/module.py @@ -405,7 +405,22 @@ class Module(MgrModule): self.log_primed = True - class Root(object): + class EndPoint(object): + def _health_data(self): + health = global_instance().get_sync_object(Health).data + # Transform the `checks` dict into a list for the convenience + # of rendering from javascript. + checks = [] + for k, v in health['checks'].iteritems(): + v['type'] = k + checks.append(v) + + checks = sorted(checks, cmp=lambda a, b: a['severity'] > b['severity']) + + health['checks'] = checks + + return health + def _toplevel_data(self): """ Data consumed by the base.html template @@ -439,6 +454,7 @@ class Module(MgrModule): 'filesystems': filesystems } + class Root(EndPoint): @cherrypy.expose def filesystem(self, fs_id): template = env.get_template("filesystem.html") @@ -460,57 +476,6 @@ class Module(MgrModule): def filesystem_data(self, fs_id): return global_instance().fs_status(int(fs_id)) - def _osd(self, osd_id): - #global_instance().fs_status(int(fs_id)) - osd_id = int(osd_id) - - osd_map = global_instance().get("osd_map") - - osd = None - for o in osd_map['osds']: - if o['osd'] == osd_id: - osd = o - break - - assert osd is not None # TODO 400 - - osd_spec = "{0}".format(osd_id) - - osd_metadata = global_instance().get_metadata( - "osd", osd_spec) - - result = CommandResult("") - global_instance().send_command(result, "osd", osd_spec, - json.dumps({ - "prefix": "perf histogram dump", - }), - "") - r, outb, outs = result.wait() - assert r == 0 - histogram = json.loads(outb) - - return { - "osd": osd, - "osd_metadata": osd_metadata, - "osd_histogram": histogram - } - - @cherrypy.expose - def osd_perf(self, osd_id): - template = env.get_template("osd_perf.html") - toplevel_data = self._toplevel_data() - - return template.render( - ceph_version=global_instance().version, - toplevel_data=json.dumps(toplevel_data, indent=2), - content_data=json.dumps(self._osd(osd_id), indent=2) - ) - - @cherrypy.expose - @cherrypy.tools.json_out() - def osd_perf_data(self, osd_id): - return self._osd(osd_id) - def _clients(self, fs_id): cephfs_clients = global_instance().cephfs_clients.get(fs_id, None) if cephfs_clients is None: @@ -649,21 +614,6 @@ class Module(MgrModule): def servers_data(self): return self._servers() - def _health_data(self): - health = global_instance().get_sync_object(Health).data - # Transform the `checks` dict into a list for the convenience - # of rendering from javascript. - checks = [] - for k, v in health['checks'].iteritems(): - v['type'] = k - checks.append(v) - - checks = sorted(checks, cmp=lambda a, b: a['severity'] > b['severity']) - - health['checks'] = checks - - return health - def _health(self): # Fuse osdmap with pg_summary to get description of pools # including their PG states @@ -799,7 +749,148 @@ class Module(MgrModule): } } log.info("Serving static from {0}".format(static_dir)) + + class OSDEndpoint(EndPoint): + def _osd(self, osd_id): + osd_id = int(osd_id) + + osd_map = global_instance().get("osd_map") + + osd = None + for o in osd_map['osds']: + if o['osd'] == osd_id: + osd = o + break + + assert osd is not None # TODO 400 + + osd_spec = "{0}".format(osd_id) + + osd_metadata = global_instance().get_metadata( + "osd", osd_spec) + + result = CommandResult("") + global_instance().send_command(result, "osd", osd_spec, + json.dumps({ + "prefix": "perf histogram dump", + }), + "") + r, outb, outs = result.wait() + assert r == 0 + histogram = json.loads(outb) + + return { + "osd": osd, + "osd_metadata": osd_metadata, + "osd_histogram": histogram + } + + @cherrypy.expose + def perf(self, osd_id): + template = env.get_template("osd_perf.html") + toplevel_data = self._toplevel_data() + + return template.render( + ceph_version=global_instance().version, + toplevel_data=json.dumps(toplevel_data, indent=2), + content_data=json.dumps(self._osd(osd_id), indent=2) + ) + + @cherrypy.expose + @cherrypy.tools.json_out() + def perf_data(self, osd_id): + return self._osd(osd_id) + + @cherrypy.expose + @cherrypy.tools.json_out() + def list_data(self): + return self._osds_by_server() + + def _osd_summary(self, osd_id, osd_info): + """ + The info used for displaying an OSD in a table + """ + + osd_spec = "{0}".format(osd_id) + + result = {} + result['id'] = osd_id + result['stats'] = {} + result['stats_history'] = {} + + # Counter stats + for s in ['osd.op_w', 'osd.op_in_bytes', 'osd.op_r', 'osd.op_out_bytes']: + result['stats'][s.split(".")[1]] = global_instance().get_rate('osd', osd_spec, s) + result['stats_history'][s.split(".")[1]] = \ + global_instance().get_counter('osd', osd_spec, s)[s] + + # Gauge stats + for s in ["osd.numpg", "osd.stat_bytes", "osd.stat_bytes_used"]: + result['stats'][s.split(".")[1]] = global_instance().get_latest('osd', osd_spec, s) + + result['up'] = osd_info['up'] + result['in'] = osd_info['in'] + + result['url'] = "/osd/perf/{0}".format(osd_id) + + return result + + def _osds_by_server(self): + result = defaultdict(list) + servers = global_instance().list_servers() + + osd_map = global_instance().get_sync_object(OsdMap) + + for server in servers: + hostname = server['hostname'] + services = server['services'] + first = True + for s in services: + if s["type"] == "osd": + osd_id = int(s["id"]) + # If metadata doesn't tally with osdmap, drop it. + if osd_id not in osd_map.osds_by_id: + global_instance().log.warn( + "OSD service {0} missing in OSDMap, stale metadata?".format(osd_id)) + continue + summary = self._osd_summary(osd_id, + osd_map.osds_by_id[osd_id]) + + if first: + # A little helper for rendering + summary['first'] = True + first = False + result[hostname].append(summary) + + global_instance().log.warn("result.size {0} servers.size {1}".format( + len(result), len(servers) + )) + + # Return list form for convenience of rendering + return result.items() + + @cherrypy.expose + def index(self): + """ + List of all OSDS grouped by host + :return: + """ + + template = env.get_template("osds.html") + toplevel_data = self._toplevel_data() + + content_data = { + "osds_by_server": self._osds_by_server() + } + + return template.render( + ceph_version=global_instance().version, + toplevel_data=json.dumps(toplevel_data, indent=2), + content_data=json.dumps(content_data, indent=2) + ) + cherrypy.tree.mount(Root(), "/", conf) + cherrypy.tree.mount(OSDEndpoint(), "/osd", conf) log.info("Starting engine...") cherrypy.engine.start() diff --git a/src/pybind/mgr/dashboard/osd_perf.html b/src/pybind/mgr/dashboard/osd_perf.html index 8c92414cf02..7ab958effb5 100644 --- a/src/pybind/mgr/dashboard/osd_perf.html +++ b/src/pybind/mgr/dashboard/osd_perf.html @@ -7,8 +7,6 @@ // Pre-populated initial data at page load var content_data = {{ content_data }}; - - var hexdigits = function(v) { var i = Math.floor(v * 255); if (Math.floor(i) < 0x10) { @@ -98,7 +96,7 @@ post_load(); var refresh = function() { - $.get("/osd_perf_data/" + content_data.osd.osd + "/", function(data) { + $.get("/osd/perf_data/" + content_data.osd.osd + "/", function(data) { _.extend(content_data.osd_histogram, data.osd_histogram); _.extend(content_data.osd, data.osd); _.extend(content_data.osd_metadata, data.osd_metadata); diff --git a/src/pybind/mgr/dashboard/osds.html b/src/pybind/mgr/dashboard/osds.html new file mode 100644 index 00000000000..ddcb8577d95 --- /dev/null +++ b/src/pybind/mgr/dashboard/osds.html @@ -0,0 +1,105 @@ +{% extends "base.html" %} + +{% block content %} + + + +
    +

    + OSD daemons +

    +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    HostIDStatusPGsUsageRead bytesWrite bytesRead opsWrite ops
    {server.0}{osd.id}{osd.stats.numpg}{osd.stats.stat_bytes_used | dimless_binary} / {osd.stats.stat_bytes | dimless_binary}{osd.stats.op_out_bytes | dimless_binary}/s {osd.stats.op_in_bytes | dimless_binary}/s {osd.stats.op_r | dimless}/s{osd.stats.op_w | dimless}/s
    + + + + +
    +
    + + +
    + + +{% endblock %} -- 2.39.5