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
'filesystems': filesystems
}
+ class Root(EndPoint):
@cherrypy.expose
def filesystem(self, fs_id):
template = env.get_template("filesystem.html")
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:
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
}
}
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()
--- /dev/null
+{% extends "base.html" %}
+
+{% block content %}
+
+<script>
+ $(document).ready(function(){
+ // Pre-populated initial data at page load
+ var content_data = {{ content_data }};
+
+ var refresh = function() {
+ $.get("/osd/list_data/", function(data) {
+ content_data.osds_by_server = data;
+ $('.inlinesparkline').sparkline();
+ setTimeout(refresh, 5000);
+ });
+ };
+
+ rivets.formatters.colored_up_in = function(osd){
+ var result = "";
+ if (osd.up) {
+ result += "<span style='color:#00bb00;'>up</span>";
+ } else {
+ result += "<span style='color:#bb0000;'>down</span>";
+ }
+
+ result += ", ";
+
+ if (osd.in) {
+ result += "<span style='color:#00bb00;'>in</span>";
+ } else {
+ result += "<span style='color:#bb0000;'>out</span>";
+ }
+
+ return result;
+ };
+
+ rivets.formatters.sparkline_data = function(time_series) {
+ result = "";
+ for (var i = 1; i < time_series.length; ++i) {
+ var delta_v = time_series[i][1] - time_series[i - 1][1];
+ var delta_t = time_series[i][0] - time_series[i - 1][0];
+ result += (delta_v / delta_t + ",");
+ }
+ return result;
+ };
+
+ rivets.bind($("div#content"), content_data);
+ $('.inlinesparkline').sparkline();
+ setTimeout(refresh, 5000);
+ });
+</script>
+
+<section class="content-header">
+ <h1>
+ OSD daemons
+ </h1>
+</section>
+
+<section class="content">
+ <div class="box">
+ <div class="box-body">
+
+ <table class="table table-condensed table-bordered">
+ <thead>
+ <tr>
+ <th>Host</th>
+ <th>ID</th>
+ <th>Status</th>
+ <th>PGs</th>
+ <th>Usage</th>
+ <th>Read bytes</th>
+ <th>Write bytes</th>
+ <th>Read ops</th>
+ <th>Write ops</th>
+ </tr>
+ </thead>
+
+ <tbody rv-each-server="osds_by_server">
+ <tr rv-each-osd="server.1">
+ <td rv-if="osd.first" rv-rowspan="server.1 | length">{server.0}</td>
+ <td><a rv-href="osd.url">{osd.id}</a></td>
+ <td rv-html="osd | colored_up_in"></td>
+ <td>{osd.stats.numpg}</td>
+ <td>{osd.stats.stat_bytes_used | dimless_binary} / {osd.stats.stat_bytes | dimless_binary}</td>
+ <td>{osd.stats.op_out_bytes | dimless_binary}/s <span class="inlinesparkline" rv-html="osd.stats_history.op_out_bytes | sparkline_data"></span></td>
+ <td>{osd.stats.op_in_bytes | dimless_binary}/s <span class="inlinesparkline" rv-html="osd.stats_history.op_in_bytes | sparkline_data"></span></td>
+ <td>{osd.stats.op_r | dimless}/s</td>
+ <td>{osd.stats.op_w | dimless}/s</td>
+ </tr>
+ </tbody>
+
+
+ </table>
+
+
+
+
+ </div>
+ </div>
+
+
+</section>
+<!-- /.content -->
+
+{% endblock %}