luci-app-statistics: rearrange graph display
authorJo-Philipp Wich <jo@mein.io>
Sat, 22 Feb 2020 22:32:44 +0000 (23:32 +0100)
committerJo-Philipp Wich <jo@mein.io>
Sat, 22 Feb 2020 22:32:44 +0000 (23:32 +0100)
 - 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 <jo@mein.io>
applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool.js
applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/cpufreq.js
applications/luci-app-statistics/htdocs/luci-static/resources/statistics/rrdtool/definitions/tcpconns.js
applications/luci-app-statistics/htdocs/luci-static/resources/view/statistics/graphs.js

index fbb5da626d58a5d2e0e6822f66db21720f3db490..00b65d5b6d87d42fe35ab5fd682baf6be4433896 100644 (file)
@@ -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);
                                        }
                                }
                        }
index c12260ea9d9308d854a9306861dbb783731fd482..6f4d52deb2aec8e0a21f9c8da8c3bb7270be0f18 100644 (file)
@@ -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",
index f2a2d7c2223bf244234b1fd3b185fa27f9f8ca7a..4ecd532fa038bee22de20734977008717ddbb2f1 100644 (file)
@@ -21,6 +21,11 @@ return L.Class.extend({
                                        options: {
                                                load__ESTABLISHED: { title: "%di", noarea: true }
                                        }
+                               },
+                               options: {
+                                       tcp_connections__value: {
+                                               title: '%di'
+                                       }
                                }
                        }
                };
index 2abe78dac74cd091c83406543d0aba16dbf15842..9da94681d277d5c9352dcc26432eae139b38a395 100644 (file)
@@ -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' })) });