From 72da3c4c5509a4fe9d779e03d76a97854022fafe Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Sat, 22 Feb 2020 23:32:44 +0100 Subject: [PATCH] luci-app-statistics: rearrange graph display - Add a per-plugin overview tab that contains the (non-detail) graphs of each plugin instance, similar to the old Lua based implementation - Numerically order plugin instances starting with numbers - Avoid multiple renderings of the same graphs - Fix legend of tcpconns graphs - Move cpufreq distribution and transition charts to detail tabs Signed-off-by: Jo-Philipp Wich --- .../resources/statistics/rrdtool.js | 42 ++++++++++++++-- .../statistics/rrdtool/definitions/cpufreq.js | 2 + .../rrdtool/definitions/tcpconns.js | 5 ++ .../resources/view/statistics/graphs.js | 48 +++++++++++++++---- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool.js b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool.js index fbb5da626d..00b65d5b6d 100644 --- a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool.js +++ b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool.js @@ -259,7 +259,17 @@ return L.Class.extend({ }, pluginInstances: function(hostInstance, pluginName) { - return Object.keys((rrdtree[hostInstance] || {})[pluginName] || {}).sort(); + return Object.keys((rrdtree[hostInstance] || {})[pluginName] || {}).sort(function(a, b) { + var x = a.match(/^(\d+)\b/), + y = b.match(/^(\d+)\b/); + + if (!x != !y) + return !x - !y; + else if (x && y && x[0] != y[0]) + return +x[0] - +y[0]; + else + return a > b; + }); }, dataTypes: function(hostInstance, pluginName, pluginInstance) { @@ -279,6 +289,21 @@ return L.Class.extend({ return (graphdefs[pluginName] != null); }, + hasInstanceDetails: function(hostInstance, pluginName, pluginInstance) { + var def = graphdefs[pluginName]; + + if (!def || typeof(def.rrdargs) != 'function') + return false; + + var optlist = this._forcelol(def.rrdargs(this, hostInstance, pluginName, pluginInstance, null, false)); + + for (var i = 0; i < optlist.length; i++) + if (optlist[i].detail) + return true; + + return false; + }, + _mkpath: function(host, plugin, plugin_instance, dtype, data_instance) { var path = host + '/' + plugin; @@ -304,7 +329,7 @@ return L.Class.extend({ return L.isObject(list[0]) ? list : [ list ]; }, - _rrdtool: function(def, rrd, timespan, width, height) { + _rrdtool: function(def, rrd, timespan, width, height, cache) { var cmdline = [ 'graph', '-', '-a', 'PNG', '-s', 'NOW-%s'.format(timespan || this.opts.timespan), @@ -322,6 +347,15 @@ return L.Class.extend({ cmdline.push(opt); } + if (L.isObject(cache)) { + var key = sfh(cmdline.join('\0')); + + if (!cache.hasOwnProperty(key)) + cache[key] = fs.exec_direct('/usr/bin/rrdtool', cmdline, 'blob', true); + + return cache[key]; + } + return fs.exec_direct('/usr/bin/rrdtool', cmdline, 'blob', true); }, @@ -682,7 +716,7 @@ return L.Class.extend({ return defs; }, - render: function(plugin, plugin_instance, is_index, hostname, timespan, width, height) { + render: function(plugin, plugin_instance, is_index, hostname, timespan, width, height, cache) { var pngs = []; /* check for a whole graph handler */ @@ -705,7 +739,7 @@ return L.Class.extend({ /* render all diagrams */ for (var j = 0; j < diagrams.length; j++) { /* exec */ - _images[i][j] = this._rrdtool(diagrams[j], null, timespan, width, height); + _images[i][j] = this._rrdtool(diagrams[j], null, timespan, width, height, cache); } } } diff --git a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/cpufreq.js b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/cpufreq.js index c12260ea9d..6f4d52deb2 100644 --- a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/cpufreq.js +++ b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/cpufreq.js @@ -22,6 +22,7 @@ return L.Class.extend({ if (uci.get("luci_statistics", "collectd_cpufreq", "ExtraItems")) { var transitions = { + detail: true, title: "%H: Frequency transitions - core %pi", alt_autoscale: true, vlabel: "Transitions", @@ -35,6 +36,7 @@ return L.Class.extend({ }; var percentage = { + detail: true, title: "%H: Frequency distribution - core %pi", alt_autoscale: true, vlabel: "Percent", diff --git a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/tcpconns.js b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/tcpconns.js index f2a2d7c222..4ecd532fa0 100644 --- a/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/tcpconns.js +++ b/applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/tcpconns.js @@ -21,6 +21,11 @@ return L.Class.extend({ options: { load__ESTABLISHED: { title: "%di", noarea: true } } + }, + options: { + tcp_connections__value: { + title: '%di' + } } } }; diff --git a/applications/luci-app-statistics/htdocs/luci-static/resources/view/statistics/graphs.js b/applications/luci-app-statistics/htdocs/luci-static/resources/view/statistics/graphs.js index 2abe78dac7..9da94681d2 100644 --- a/applications/luci-app-statistics/htdocs/luci-static/resources/view/statistics/graphs.js +++ b/applications/luci-app-statistics/htdocs/luci-static/resources/view/statistics/graphs.js @@ -14,8 +14,11 @@ return L.view.extend({ updatePluginTab: function(host, span, time, ev) { var container = ev.target, + width = Math.max(200, container.offsetWidth - 100), plugin = ev.detail.tab, - plugin_instances = rrdtool.pluginInstances(host.value, plugin); + render_instances = [], + plugin_instances = rrdtool.pluginInstances(host.value, plugin), + cache = {}; activePlugin = plugin; @@ -25,20 +28,41 @@ return L.view.extend({ ]) ]); - Promise.all(plugin_instances.map(function(instance) { - return rrdtool.render(plugin, instance, false, host.value, span.value, Math.max(200, container.offsetWidth - 100)); + for (var i = 0; i < plugin_instances.length; i++) + if (rrdtool.hasInstanceDetails(host.value, plugin, plugin_instances[i])) + render_instances.push(plugin_instances[i]); + + if (render_instances.length == 0 || render_instances.length > 1) + render_instances.unshift('-'); + + Promise.all(render_instances.map(function(instance) { + if (instance == '-') { + var tasks = []; + + for (var i = 0; i < plugin_instances.length; i++) + tasks.push(rrdtool.render(plugin, plugin_instances[i], true, host.value, span.value, width, null, cache)); + + return Promise.all(tasks).then(function(blobs) { + return Array.prototype.concat.apply([], blobs); + }); + } + else { + return rrdtool.render(plugin, instance, false, host.value, span.value, width, null, cache); + } })).then(function(blobs) { var multiple = blobs.length > 1; L.dom.content(container, E('div', {}, blobs.map(function(blobs, i) { - var plugin_instance = plugin_instances[i]; + var plugin_instance = i ? plugin_instances[i-1] : plugin_instances.join('|'), + title = '%s: %s'.format(rrdtool.pluginTitle(plugin), i ? plugin_instance : _('Overview')); return E('div', { 'class': 'center', 'data-tab': multiple ? i : null, - 'data-tab-title': multiple ? '%s: %s'.format(rrdtool.pluginTitle(plugin), plugin_instances[i]) : null, + 'data-tab-title': multiple ? title : null, 'data-plugin': plugin, - 'data-plugin-instance': plugin_instances[i], + 'data-plugin-instance': plugin_instance, + 'data-is-index': i ? null : true, 'cbi-tab-active': function(ev) { activeInstance = ev.target.getAttribute('data-plugin-instance') } }, blobs.map(function(blob) { return E('img', { @@ -50,7 +74,7 @@ return L.view.extend({ if (multiple) ui.tabs.initTabGroup(container.lastElementChild.childNodes); else - activeInstance = plugin_instances[0]; + activeInstance = plugin_instances.join('|'); }); }, @@ -87,9 +111,15 @@ return L.view.extend({ }, refreshGraphs: function(host, span, time, container) { - var div = document.querySelector('[data-plugin="%s"][data-plugin-instance="%s"]'.format(activePlugin, activeInstance || '')); + var div = document.querySelector('[data-plugin="%s"][data-plugin-instance="%s"]'.format(activePlugin, activeInstance || '')), + width = Math.max(200, container.offsetWidth - 100), + render_instances = activeInstance.split(/\|/); - return rrdtool.render(activePlugin, activeInstance || '', false, host.value, span.value, Math.max(200, container.offsetWidth - 100)).then(function(blobs) { + return Promise.all(render_instances.map(function(render_instance) { + return rrdtool.render(activePlugin, render_instance || '', div.hasAttribute('data-is-index'), host.value, span.value, width); + })).then(function(blobs) { + return Array.prototype.concat.apply([], blobs); + }).then(function(blobs) { return Promise.all(blobs.map(function(blob) { return new Promise(function(resolveFn, rejectFn) { var img = E('img', { 'src': URL.createObjectURL(new Blob([blob], { type: 'image/png' })) }); -- 2.30.2