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. :(
This commit is contained in:
Kyle Kingsbury 2014-05-22 13:47:28 -07:00
parent 68c7c4e3be
commit 2497b2b119

View file

@ -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 @@
"<label for='cols'>Columns</label>" +
"<input type='text' name='cols' value=\"{{-cols_str}}\" /><br />" +
"<span class='desc'>'host' or 'service'</span><br />" +
"<label for='row_sort'>Sort rows</label>" +
"<select name='row_sort'>" +
"<option value='lexical' {{row_sort_lexical}}>Lexically</option>" +
"<option value='metric' {{row_sort_metric}}>By metric</option>" +
"</select><br />" +
"<label for='col_sort'>Sort columns</label>" +
"<select name='col_sort'>" +
"<option value='lexical' {{col_sort_lexical}}>Lexically</option>" +
"<option value='metric' {{col_sort_metric}}>By metric</option>" +
"</select><br />" +
"<label for='max'>Max</label>" +
"<input type='text' name='max' value=\"{{-max}}\" /><br />" +
"<span class='desc'>'all', 'host', 'service', or any number.</span>"
);
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 $('<td></td>');
}
// Generates a td cell for an event. Returns a map of the td, bar, and metric
// elements.
Grid.prototype.newTdCache = function() {
var td = $('<td><span class="bar"><span class="metric"/></span></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 = $("<tr><th></th>");
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();
};