From 2497b2b119ccc2b3052f96cd5cb6752421dc5a4a Mon Sep 17 00:00:00 2001 From: Kyle Kingsbury Date: Thu, 22 May 2014 13:47:28 -0700 Subject: [PATCH] Grid: fix N bugs, metric sorting, slower. Had to kill the caching code; it's been the source of no end of bugs in the grid for the last six months and I'm just not smart enough to fix it. Every call goes through (slurred) render now. :( --- lib/riemann/dash/public/views/grid.js | 178 ++++++++++++-------------- 1 file changed, 79 insertions(+), 99 deletions(-) diff --git a/lib/riemann/dash/public/views/grid.js b/lib/riemann/dash/public/views/grid.js index abbd79c..5d2de8a 100644 --- a/lib/riemann/dash/public/views/grid.js +++ b/lib/riemann/dash/public/views/grid.js @@ -3,7 +3,7 @@ var Grid = function(json) { // We want a per-grid slurred rendering. - this.render = util.slur(200, this.render); + this.render = util.slur(500, this.render); view.View.call(this, json); this.query = json.query; @@ -12,6 +12,8 @@ this.rows_str = json.rows; this.cols_str = json.cols; this.max_fn = util.max_fn(json.max); + this.row_sort = json.row_sort || "lexical"; + this.col_sort = json.col_sort || "lexical"; this.row_fn = util.extract_fn(json.rows) || util.extract_fn('host'); this.col_fn = util.extract_fn(json.cols) || util.extract_fn('service'); this.clickFocusable = true; @@ -30,8 +32,6 @@ this.rows = []; // events[row_key][col_key] = event this.events = {}; - // elCache[row_key][col_key] = {td: , metric: } - this.elCache = {}; // maxima[maxima_value] = 500 this.maxima = {}; @@ -55,7 +55,9 @@ query: this.query, max: this.max, rows: this.rows_str, - cols: this.cols_str + cols: this.cols_str, + row_sort: this.row_sort, + col_sort: this.col_sort }); }; @@ -69,13 +71,29 @@ "" + "
" + "'host' or 'service'
" + + "" + + "
" + + "" + + "
" + "" + "
" + "'all', 'host', 'service', or any number." ); Grid.prototype.editForm = function() { - return editTemplate(this); + return editTemplate( + util.merge(this, { + row_sort_lexical: (this.row_sort === "lexical" ? "selected" : ""), + row_sort_metric: (this.row_sort === "metric" ? "selected" : ""), + col_sort_lexical: (this.col_sort === "lexical" ? "selected" : ""), + col_sort_metric: (this.col_sort === "metric" ? "selected" : "") + })); }; // What is the maximum for this event? @@ -85,24 +103,29 @@ return this.max_fn; } else { // Use fn to group maxima - return this.maxima[this.max_fn(event)] || -1/0; + return this.maxima[this.max_fn(event)] || 1/0; } }; // Recomputes all maxima. Grid.prototype.refreshMaxima = function() { + if (typeof(this.max_fn) === "number") { + // We're done; no need to update a fixed maximum. + return; + } + this.maxima = {}; var e; var max_key; var current_max; - for (var name in this.events) { - for (var subName in this.events[name]) { - e = this.events[name][subName]; + for (var row in this.events) { + for (var col in this.events[row]) { + e = this.events[row][col]; if (e.metric) { max_key = this.max_fn(e); current_max = this.maxima[max_key]; if ((current_max === undefined) || (current_max < e.metric)) { - this.maxima[current_max] = e.metric; + this.maxima[max_key] = e.metric; } } } @@ -151,101 +174,80 @@ var cols = {}; for (var row in this.events) { for (var col in this.events[row]) { - cols[col] = true; + cols[col] = Math.max( + (cols[col] || -1/0), this.events[row][col].metric); } } - this.cols = _.keys(cols).sort(); - } + if (this.col_sort === "lexical") { + this.cols = _.keys(cols).sort(); + } else { + this.cols = _.sortBy(_.keys(cols), function(c) { return -cols[c]; }); + } + }; // Full refresh of row list Grid.prototype.refreshRows = function() { - // Recompute rows - this.rows = _.keys(this.events).sort(); - } + if (this.row_sort === "lexical") { + this.rows = _.keys(this.events).sort(); + } else { + var rows = {}; + for (var row in this.events) { + for (var col in this.events[row]) { + rows[row] = Math.max( + (rows[row] || -1/0), this.events[row][col].metric); + } + } + this.rows = _.sortBy(_.keys(rows), function(r) { return -rows[r]; }); + } + }; + // Returns a td for the given element. + Grid.prototype.renderElement = function(event) { + if (event === undefined) { + // Nuke element + return $(''); + } - // Generates a td cell for an event. Returns a map of the td, bar, and metric - // elements. - Grid.prototype.newTdCache = function() { var td = $(''); var bar = td.find('.bar'); var metric = td.find('.metric'); - return {td: td, bar: bar, metric: metric}; - }; - - // Update a single jq element with information about an event. - Grid.prototype.renderElement = function(e, event) { - if (event === undefined) { - // Nuke element - e.metric.text(''); - e.bar.css('width', 0); - e.td.attr('class', ''); - e.td.attr('title', ''); - return false; - } // State - e.td.attr('class', "state box " + event.state); + td.attr('class', "state box " + event.state); // Description - e.td.attr('title', event.host + ' ' + event.service + "\n" + event.state + + td.attr('title', event.host + ' ' + event.service + "\n" + event.state + "\nreceived at " + new Date(event.time).toString() + "\nexpiring at " + new Date(event.time + event.ttl * 1000).toString() + (event.description ? ("\n\n" + event.description) : "")); // Metric if (event.metric != undefined) { - e.metric.text(format.float(event.metric)); + metric.text(format.float(event.metric)); } else if (event.state != undefined) { - e.metric.text(event.state); + metric.text(event.state); } // Bar chart - if (event.metric === 0) { - // Zero - e.bar.css('width', 0); - } else if (0 < event.metric) { - // Positive - e.bar.css('width', - (event.metric / this.eventMax(event) * 100) + "%"); + if (event.metric === null || + event.metric === undefined || + event.metric <= 0) { + bar.css('width', 0); } else { - // Nil or negative - e.bar.css('width', 0); - } - }; - - // Render a single event if there's been no change to table structure. - Grid.prototype.partialRender = function(event) { - var rowKey = this.row_fn(event); - var colKey = this.col_fn(event); - var cache = (this.elCache[rowKey] && this.elCache[rowKey][colKey]); - if (!cache) { - // No cached td element - cache = this.newTdCache(); - - // Update cache - if (!this.elCache[rowKey]) { - this.elCache[rowKey] = {}; - } - this.elCache[rowKey][colKey] = cache; - - // Update table. - var table = this.el.find('table'); - var rowIndex = this.rows.indexOf(rowKey); - var columnIndex = this.cols.indexOf(colKey); - var row = this.el.find('tbody tr')[rowIndex]; - $($(row).find('td')[columnIndex]).replaceWith(cache.td); + // Positive + bar.css('width', + (event.metric / this.eventMax(event) * 100) + "%"); } - this.renderElement(cache, event); + return td; }; // A full re-rendering of the table. Grid.prototype.render = function() { // Update data model + this.refreshMaxima(); this.refreshRows(); this.refreshCols(); - this.refreshMaxima(); var table = this.el.find('table'); table.empty(); @@ -262,28 +264,13 @@ row.append(element); }); - this.rows.forEach(function(name, i) { + this.rows.forEach(function(rowName, i) { row = $(""); - table.append(row); row.find('th').text(shortRowNames[i] || 'nil'); - this.cols.forEach(function(subName) { - var event = this.events[name][subName]; - var cache = (this.elCache[name] && this.elCache[name][subName]); - if (!cache) { - // Not cached; generate and render. - cache = this.newTdCache(); - - // Cache element - if (!this.elCache[name]) { - this.elCache[name] = {}; - } - this.elCache[name][subName] = cache; - - // Render element - this.renderElement(cache, event); - } - row.append(cache.td); + this.cols.forEach(function(colName) { + row.append(this.renderElement(this.events[rowName][colName])); }, this); + table.append(row); }, this); }; @@ -314,7 +301,8 @@ if (newEvent || newMax) { this.render(); } else { - this.partialRender(e); +// this.partialRender(e); + this.render(); } }; @@ -331,14 +319,6 @@ } } - // Wipe element cache - if (this.elCache[row_key]) { - delete this.elCache[row_key][col_key]; - if (_.isEmpty(this.elCache[row_key])) { - delete this.elCache[row_key]; - } - } - this.render(); };