riemann-dash 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +31 -9
- data/README.markdown +18 -2
- data/example/config.rb +5 -0
- data/lib/riemann/dash.rb +1 -0
- data/lib/riemann/dash/app.rb +1 -0
- data/lib/riemann/dash/browser_config.rb +72 -0
- data/lib/riemann/dash/browser_config/file.rb +39 -0
- data/lib/riemann/dash/browser_config/s3.rb +30 -0
- data/lib/riemann/dash/config.rb +21 -41
- data/lib/riemann/dash/controller/index.rb +4 -4
- data/lib/riemann/dash/public/dash.js +4 -1
- data/lib/riemann/dash/public/eventPane.js +63 -0
- data/lib/riemann/dash/public/keys.js +1 -1
- data/lib/riemann/dash/public/strings.js +14 -2
- data/lib/riemann/dash/public/subs.js +13 -11
- data/lib/riemann/dash/public/util.js +41 -3
- data/lib/riemann/dash/public/view.js +56 -32
- data/lib/riemann/dash/public/views/flot.js +6 -1
- data/lib/riemann/dash/public/views/gauge.js +14 -1
- data/lib/riemann/dash/public/views/grid.js +155 -235
- data/lib/riemann/dash/public/views/help.js +1 -1
- data/lib/riemann/dash/public/views/list.js +73 -0
- data/lib/riemann/dash/public/views/log.js +3 -0
- data/lib/riemann/dash/version.rb +1 -1
- data/lib/riemann/dash/views/css.scss +60 -1
- data/lib/riemann/dash/views/index.erubis +3 -0
- data/riemann-dash.gemspec +2 -3
- metadata +15 -24
@@ -1,47 +1,9 @@
|
|
1
1
|
(function() {
|
2
2
|
var fitopts = {min: 6, max: 1000};
|
3
3
|
|
4
|
-
// Takes a string and returns a function which extracts
|
5
|
-
// a value from an event.
|
6
|
-
var extract_fn = function(str) {
|
7
|
-
// When null/undefined, stay null/undefined.
|
8
|
-
if (! str) {
|
9
|
-
return str;
|
10
|
-
}
|
11
|
-
|
12
|
-
// Probably the worst hack ever. I'm not documenting this because it's so
|
13
|
-
// evil--though tremendously useful.
|
14
|
-
if (str.match(/^fn /)) {
|
15
|
-
// Grab the rest of the string, turn it into an anonymous fn taking a
|
16
|
-
// single arg `e`.
|
17
|
-
return Function.apply(null, ['e', str.substring(3)]);
|
18
|
-
}
|
19
|
-
|
20
|
-
// Property access
|
21
|
-
return function(e) {
|
22
|
-
return e[str];
|
23
|
-
};
|
24
|
-
};
|
25
|
-
|
26
|
-
// Takes a string and returns either:
|
27
|
-
// - a function which extracts a maximum value from an event.
|
28
|
-
// - a number to be used as the constant maximum.
|
29
|
-
var max_fn = function(str) {
|
30
|
-
if ((!str) || str === "all") {
|
31
|
-
// Always the same value: global maxima
|
32
|
-
return function(e) { return "all"; };
|
33
|
-
}
|
34
|
-
if (isNaN(parseFloat(str))) {
|
35
|
-
// Not a number. Extract a field.
|
36
|
-
return function(e) { return e[str]; };
|
37
|
-
}
|
38
|
-
// Return a constant number.
|
39
|
-
return parseFloat(str);
|
40
|
-
};
|
41
|
-
|
42
4
|
var Grid = function(json) {
|
43
5
|
// We want a per-grid slurred rendering.
|
44
|
-
this.render = util.slur(
|
6
|
+
this.render = util.slur(500, this.render);
|
45
7
|
|
46
8
|
view.View.call(this, json);
|
47
9
|
this.query = json.query;
|
@@ -49,9 +11,11 @@
|
|
49
11
|
this.max = json.max;
|
50
12
|
this.rows_str = json.rows;
|
51
13
|
this.cols_str = json.cols;
|
52
|
-
this.max_fn = max_fn(json.max);
|
53
|
-
this.
|
54
|
-
this.
|
14
|
+
this.max_fn = util.max_fn(json.max);
|
15
|
+
this.row_sort = json.row_sort || "lexical";
|
16
|
+
this.col_sort = json.col_sort || "lexical";
|
17
|
+
this.row_fn = util.extract_fn(json.rows) || util.extract_fn('host');
|
18
|
+
this.col_fn = util.extract_fn(json.cols) || util.extract_fn('service');
|
55
19
|
this.clickFocusable = true;
|
56
20
|
|
57
21
|
// Initial display
|
@@ -62,14 +26,12 @@
|
|
62
26
|
);
|
63
27
|
this.box = this.el.find('.box');
|
64
28
|
this.el.find('h2').text(this.title);
|
65
|
-
|
29
|
+
|
66
30
|
// State
|
67
31
|
this.cols = [];
|
68
32
|
this.rows = [];
|
69
33
|
// events[row_key][col_key] = event
|
70
34
|
this.events = {};
|
71
|
-
// elCache[row_key][col_key] = {td: , metric: }
|
72
|
-
this.elCache = {};
|
73
35
|
// maxima[maxima_value] = 500
|
74
36
|
this.maxima = {};
|
75
37
|
|
@@ -93,10 +55,12 @@
|
|
93
55
|
query: this.query,
|
94
56
|
max: this.max,
|
95
57
|
rows: this.rows_str,
|
96
|
-
cols: this.cols_str
|
58
|
+
cols: this.cols_str,
|
59
|
+
row_sort: this.row_sort,
|
60
|
+
col_sort: this.col_sort
|
97
61
|
});
|
98
62
|
};
|
99
|
-
|
63
|
+
|
100
64
|
var editTemplate = _.template(
|
101
65
|
"<label for='title'>Title</label>" +
|
102
66
|
"<input type='text' name='title' value=\"{{-title}}\" /><br />" +
|
@@ -107,23 +71,29 @@
|
|
107
71
|
"<label for='cols'>Columns</label>" +
|
108
72
|
"<input type='text' name='cols' value=\"{{-cols_str}}\" /><br />" +
|
109
73
|
"<span class='desc'>'host' or 'service'</span><br />" +
|
74
|
+
"<label for='row_sort'>Sort rows</label>" +
|
75
|
+
"<select name='row_sort'>" +
|
76
|
+
"<option value='lexical' {{row_sort_lexical}}>Lexically</option>" +
|
77
|
+
"<option value='metric' {{row_sort_metric}}>By metric</option>" +
|
78
|
+
"</select><br />" +
|
79
|
+
"<label for='col_sort'>Sort columns</label>" +
|
80
|
+
"<select name='col_sort'>" +
|
81
|
+
"<option value='lexical' {{col_sort_lexical}}>Lexically</option>" +
|
82
|
+
"<option value='metric' {{col_sort_metric}}>By metric</option>" +
|
83
|
+
"</select><br />" +
|
110
84
|
"<label for='max'>Max</label>" +
|
111
85
|
"<input type='text' name='max' value=\"{{-max}}\" /><br />" +
|
112
86
|
"<span class='desc'>'all', 'host', 'service', or any number.</span>"
|
113
87
|
);
|
114
88
|
|
115
89
|
Grid.prototype.editForm = function() {
|
116
|
-
return editTemplate(
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
for (var column in this.events[row]) {
|
124
|
-
events.push(this.events[row][column]);
|
125
|
-
}
|
126
|
-
}
|
90
|
+
return editTemplate(
|
91
|
+
util.merge(this, {
|
92
|
+
row_sort_lexical: (this.row_sort === "lexical" ? "selected" : ""),
|
93
|
+
row_sort_metric: (this.row_sort === "metric" ? "selected" : ""),
|
94
|
+
col_sort_lexical: (this.col_sort === "lexical" ? "selected" : ""),
|
95
|
+
col_sort_metric: (this.col_sort === "metric" ? "selected" : "")
|
96
|
+
}));
|
127
97
|
};
|
128
98
|
|
129
99
|
// What is the maximum for this event?
|
@@ -133,109 +103,160 @@
|
|
133
103
|
return this.max_fn;
|
134
104
|
} else {
|
135
105
|
// Use fn to group maxima
|
136
|
-
|
137
|
-
return this.maxima[this.max_fn(event)] || -1/0;
|
106
|
+
return this.maxima[this.max_fn(event)] || 1/0;
|
138
107
|
}
|
139
108
|
};
|
140
109
|
|
141
|
-
//
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
110
|
+
// Recomputes all maxima.
|
111
|
+
Grid.prototype.refreshMaxima = function() {
|
112
|
+
if (typeof(this.max_fn) === "number") {
|
113
|
+
// We're done; no need to update a fixed maximum.
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
this.maxima = {};
|
118
|
+
var e;
|
119
|
+
var max_key;
|
120
|
+
var current_max;
|
121
|
+
for (var row in this.events) {
|
122
|
+
for (var col in this.events[row]) {
|
123
|
+
e = this.events[row][col];
|
124
|
+
if (e.metric) {
|
125
|
+
max_key = this.max_fn(e);
|
126
|
+
current_max = this.maxima[max_key];
|
127
|
+
if ((current_max === undefined) || (current_max < e.metric)) {
|
128
|
+
this.maxima[max_key] = e.metric;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
// Update cached maxima with a new event. Returns true if maxima changed.
|
136
|
+
Grid.prototype.updateMax = function(event) {
|
137
|
+
if (typeof(this.max_fn) === "number") {
|
138
|
+
// Absolute maximum; no need to recompute anything.
|
139
|
+
return false;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (event.metric === undefined) {
|
143
|
+
// No metric present
|
144
|
+
return false;
|
145
|
+
}
|
146
|
+
|
147
|
+
var max_key = this.max_fn(event);
|
148
|
+
|
149
|
+
if (this.maxima[max_key] && (event.metric <= this.maxima[max_key])) {
|
150
|
+
// We haven't bumped our max; no change.
|
151
|
+
return false;
|
152
|
+
}
|
153
|
+
|
154
|
+
// Traverse all events looking for a match.
|
155
|
+
var e;
|
156
|
+
var currentMax = -1/0;
|
157
|
+
for (var name in this.events) {
|
158
|
+
for (var subName in this.events[name]) {
|
159
|
+
e = this.events[name][subName];
|
160
|
+
if (e.metric && this.max_fn(e) === max_key) {
|
161
|
+
currentMax = Math.max(currentMax, e.metric);
|
162
|
+
}
|
163
|
+
}
|
164
|
+
}
|
165
|
+
|
166
|
+
// Set new maximum.
|
167
|
+
this.maxima[max_key] = currentMax;
|
168
|
+
|
169
|
+
return true;
|
170
|
+
};
|
171
|
+
|
172
|
+
// Full refresh of column list
|
173
|
+
Grid.prototype.refreshCols = function() {
|
174
|
+
var cols = {};
|
175
|
+
for (var row in this.events) {
|
176
|
+
for (var col in this.events[row]) {
|
177
|
+
cols[col] = Math.max(
|
178
|
+
(cols[col] || -1/0), this.events[row][col].metric);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
if (this.col_sort === "lexical") {
|
182
|
+
this.cols = _.keys(cols).sort();
|
183
|
+
} else {
|
184
|
+
this.cols = _.sortBy(_.keys(cols), function(c) { return -cols[c]; });
|
185
|
+
}
|
186
|
+
};
|
187
|
+
|
188
|
+
// Full refresh of row list
|
189
|
+
Grid.prototype.refreshRows = function() {
|
190
|
+
if (this.row_sort === "lexical") {
|
191
|
+
this.rows = _.keys(this.events).sort();
|
192
|
+
} else {
|
193
|
+
var rows = {};
|
194
|
+
for (var row in this.events) {
|
195
|
+
for (var col in this.events[row]) {
|
196
|
+
rows[row] = Math.max(
|
197
|
+
(rows[row] || -1/0), this.events[row][col].metric);
|
198
|
+
}
|
199
|
+
}
|
200
|
+
this.rows = _.sortBy(_.keys(rows), function(r) { return -rows[r]; });
|
201
|
+
}
|
148
202
|
};
|
149
203
|
|
150
|
-
//
|
151
|
-
Grid.prototype.renderElement = function(
|
204
|
+
// Returns a td for the given element.
|
205
|
+
Grid.prototype.renderElement = function(event) {
|
152
206
|
if (event === undefined) {
|
153
207
|
// Nuke element
|
154
|
-
|
155
|
-
e.bar.css('width', 0);
|
156
|
-
e.td.attr('class', '');
|
157
|
-
e.td.attr('title', '');
|
158
|
-
return false;
|
208
|
+
return $('<td></td>');
|
159
209
|
}
|
160
210
|
|
211
|
+
var td = $('<td><span class="bar"><span class="metric"/></span></td>');
|
212
|
+
var bar = td.find('.bar');
|
213
|
+
var metric = td.find('.metric');
|
214
|
+
|
215
|
+
// Event pane
|
216
|
+
td.click(function(_) { eventPane.show(event); });
|
217
|
+
|
161
218
|
// State
|
162
|
-
|
219
|
+
td.attr('class', "state box " + event.state);
|
163
220
|
|
164
221
|
// Description
|
165
|
-
|
222
|
+
td.attr('title', event.host + ' ' + event.service + "\n" + event.state +
|
166
223
|
"\nreceived at " + new Date(event.time).toString() +
|
167
224
|
"\nexpiring at " + new Date(event.time + event.ttl * 1000).toString() +
|
168
225
|
(event.description ? ("\n\n" + event.description) : ""));
|
169
226
|
|
170
227
|
// Metric
|
171
228
|
if (event.metric != undefined) {
|
172
|
-
|
229
|
+
metric.text(format.float(event.metric));
|
173
230
|
} else if (event.state != undefined) {
|
174
|
-
|
231
|
+
metric.text(event.state);
|
175
232
|
}
|
176
233
|
|
177
234
|
// Bar chart
|
178
|
-
if (event.metric ===
|
179
|
-
|
180
|
-
|
181
|
-
|
235
|
+
if (event.metric === null ||
|
236
|
+
event.metric === undefined ||
|
237
|
+
event.metric <= 0) {
|
238
|
+
bar.css('width', 0);
|
239
|
+
} else {
|
182
240
|
// Positive
|
183
|
-
|
241
|
+
bar.css('width',
|
184
242
|
(event.metric / this.eventMax(event) * 100) + "%");
|
185
|
-
} else {
|
186
|
-
// Nil or negative
|
187
|
-
e.bar.css('width', 0);
|
188
|
-
}
|
189
|
-
};
|
190
|
-
|
191
|
-
// Render a single event if there's been no change to table structure.
|
192
|
-
Grid.prototype.partialRender = function(event) {
|
193
|
-
var rowKey = this.row_fn(event);
|
194
|
-
var colKey = this.col_fn(event);
|
195
|
-
var cache = (this.elCache[rowKey] && this.elCache[rowKey][colKey]);
|
196
|
-
if (!cache) {
|
197
|
-
// No cached td element
|
198
|
-
cache = this.newTdCache();
|
199
|
-
|
200
|
-
// Update cache
|
201
|
-
if (!this.elCache[rowKey]) {
|
202
|
-
this.elCache[rowKey] = {};
|
203
|
-
}
|
204
|
-
this.elCache[rowKey][colKey] = cache;
|
205
|
-
|
206
|
-
// Update table.
|
207
|
-
var table = this.el.find('table');
|
208
|
-
var rowIndex = this.rows.indexOf(rowKey);
|
209
|
-
var columnIndex = this.cols.indexOf(colKey);
|
210
|
-
var row = this.el.find('tbody tr')[rowIndex];
|
211
|
-
$($(row).find('td')[columnIndex]).replaceWith(cache.td);
|
212
243
|
}
|
213
244
|
|
214
|
-
|
245
|
+
return td;
|
215
246
|
};
|
216
247
|
|
217
248
|
// A full re-rendering of the table.
|
218
249
|
Grid.prototype.render = function() {
|
250
|
+
// Update data model
|
251
|
+
this.refreshMaxima();
|
252
|
+
this.refreshRows();
|
253
|
+
this.refreshCols();
|
254
|
+
|
219
255
|
var table = this.el.find('table');
|
220
256
|
table.empty();
|
221
257
|
|
222
|
-
|
223
|
-
var
|
224
|
-
var rowPrefix = strings.commonPrefix(this.rows);
|
225
|
-
var colPrefixLen = colPrefix.length;
|
226
|
-
var rowPrefixLen = rowPrefix.length;
|
227
|
-
|
228
|
-
var shortener = function(fieldLen, s) {
|
229
|
-
var res = s;
|
230
|
-
if (s && s.length !== fieldLen) {
|
231
|
-
// Avoid trimming if it would trim the entire string.
|
232
|
-
res = s.substring(fieldLen);
|
233
|
-
}
|
234
|
-
return res;
|
235
|
-
};
|
236
|
-
|
237
|
-
var shortColNames = _.map(this.cols, _.partial(shortener, colPrefixLen));
|
238
|
-
var shortRowNames = _.map(this.rows, _.partial(shortener, rowPrefixLen));
|
258
|
+
var shortColNames = strings.shorten(strings.commonPrefix, this.cols);
|
259
|
+
var shortRowNames = strings.shorten(strings.commonPrefix, this.rows);
|
239
260
|
|
240
261
|
// Header
|
241
262
|
table.append("<thead><tr><th></th></tr></thead>");
|
@@ -246,90 +267,22 @@
|
|
246
267
|
row.append(element);
|
247
268
|
});
|
248
269
|
|
249
|
-
this.rows.forEach(function(
|
270
|
+
this.rows.forEach(function(rowName, i) {
|
250
271
|
row = $("<tr><th></th>");
|
251
|
-
table.append(row);
|
252
272
|
row.find('th').text(shortRowNames[i] || 'nil');
|
253
|
-
this.cols.forEach(function(
|
254
|
-
|
255
|
-
var cache = (this.elCache[name] && this.elCache[name][subName]);
|
256
|
-
if (!cache) {
|
257
|
-
// Not cached; generate and render.
|
258
|
-
cache = this.newTdCache();
|
259
|
-
|
260
|
-
// Cache element
|
261
|
-
if (!this.elCache[name]) {
|
262
|
-
this.elCache[name] = {};
|
263
|
-
}
|
264
|
-
this.elCache[name][subName] = cache;
|
265
|
-
|
266
|
-
// Render element
|
267
|
-
this.renderElement(cache, event);
|
268
|
-
}
|
269
|
-
row.append(cache.td);
|
273
|
+
this.cols.forEach(function(colName) {
|
274
|
+
row.append(this.renderElement(this.events[rowName][colName]));
|
270
275
|
}, this);
|
276
|
+
table.append(row);
|
271
277
|
}, this);
|
272
278
|
};
|
273
279
|
|
274
|
-
// Update cached maxima with a new event. Returns true if maxima changed.
|
275
|
-
Grid.prototype.updateMax = function(event) {
|
276
|
-
if (typeof(this.max_fn) === "number") {
|
277
|
-
// Absolute maximum; no need to recompute anything.
|
278
|
-
return false;
|
279
|
-
}
|
280
|
-
|
281
|
-
if (event === undefined) {
|
282
|
-
return false;
|
283
|
-
}
|
284
|
-
|
285
|
-
if (event.metric === undefined) {
|
286
|
-
// No metric present
|
287
|
-
return false;
|
288
|
-
}
|
289
|
-
|
290
|
-
var max_key = this.max_fn(event);
|
291
|
-
|
292
|
-
if (this.maxima[max_key] && (event.metric <= this.maxima[max_key])) {
|
293
|
-
// We haven't bumped our max; no change.
|
294
|
-
return false;
|
295
|
-
}
|
296
|
-
|
297
|
-
// Traverse all events looking for a match.
|
298
|
-
var e;
|
299
|
-
var currentMax = -1/0;
|
300
|
-
for (var name in this.events) {
|
301
|
-
for (var subName in this.events[name]) {
|
302
|
-
e = this.events[name][subName];
|
303
|
-
if (e.metric && this.max_fn(e) === max_key) {
|
304
|
-
currentMax = Math.max(currentMax, e.metric);
|
305
|
-
}
|
306
|
-
}
|
307
|
-
}
|
308
|
-
|
309
|
-
// Set new maximum.
|
310
|
-
this.maxima[max_key] = currentMax;
|
311
|
-
|
312
|
-
return true;
|
313
|
-
};
|
314
|
-
|
315
280
|
// Stores an event in the internal state tables. Returns true if we
|
316
281
|
// haven't seen this host/service before.
|
317
282
|
Grid.prototype.saveEvent = function(e) {
|
318
283
|
var row_key = this.row_fn(e);
|
319
284
|
var col_key = this.col_fn(e);
|
320
285
|
|
321
|
-
// Update row list
|
322
|
-
if (this.rows.indexOf(row_key) === -1) {
|
323
|
-
this.rows.push(row_key);
|
324
|
-
this.rows = _.uniq(this.rows.sort(), true);
|
325
|
-
}
|
326
|
-
|
327
|
-
// Update column list
|
328
|
-
if (this.cols.indexOf(col_key) === -1) {
|
329
|
-
this.cols.push(col_key);
|
330
|
-
this.cols = _.uniq(this.cols.sort(), true);
|
331
|
-
}
|
332
|
-
|
333
286
|
// Update events map
|
334
287
|
if (this.events[row_key] === undefined) {
|
335
288
|
// New row
|
@@ -345,14 +298,14 @@
|
|
345
298
|
|
346
299
|
// Add an event.
|
347
300
|
Grid.prototype.add = function(e) {
|
348
|
-
console.log("Adding", e);
|
349
301
|
var newEvent = this.saveEvent(e);
|
350
302
|
var newMax = this.updateMax(e);
|
351
303
|
|
352
304
|
if (newEvent || newMax) {
|
353
305
|
this.render();
|
354
306
|
} else {
|
355
|
-
this.partialRender(e);
|
307
|
+
// this.partialRender(e);
|
308
|
+
this.render();
|
356
309
|
}
|
357
310
|
};
|
358
311
|
|
@@ -369,49 +322,16 @@
|
|
369
322
|
}
|
370
323
|
}
|
371
324
|
|
372
|
-
// Wipe element cache
|
373
|
-
if (this.elCache[row_key]) {
|
374
|
-
delete this.elCache[row_key][col_key];
|
375
|
-
if (_.isEmpty(this.elCache[row_key])) {
|
376
|
-
delete this.elCache[row_key];
|
377
|
-
}
|
378
|
-
}
|
379
|
-
|
380
|
-
// Recompute rows
|
381
|
-
this.rows = _.keys(this.events).sort();
|
382
|
-
|
383
|
-
// Recompute cols
|
384
|
-
var cols = {};
|
385
|
-
for (var row in this.events) {
|
386
|
-
for (var col in this.events[row]) {
|
387
|
-
cols[col] = true;
|
388
|
-
}
|
389
|
-
}
|
390
|
-
this.cols = _.keys(cols).sort();
|
391
|
-
|
392
|
-
this.updateMax();
|
393
325
|
this.render();
|
394
326
|
};
|
395
327
|
|
396
328
|
// Accept an event.
|
397
329
|
Grid.prototype.update = function(e) {
|
398
|
-
console.log("Update", e);
|
399
330
|
if (e.state === "expired") {
|
400
331
|
this.remove(e);
|
401
332
|
} else {
|
402
333
|
this.add(e);
|
403
334
|
}
|
404
|
-
console.log("elCache", this.elCache);
|
405
|
-
console.log("events", this.events);
|
406
|
-
console.log("rows", this.rows);
|
407
|
-
console.log("cols", this.cols);
|
408
|
-
};
|
409
|
-
|
410
|
-
Grid.prototype.reflow = function() {
|
411
|
-
// this.el.find('table').height(
|
412
|
-
// this.height() -
|
413
|
-
// this.el.find('h2').height()
|
414
|
-
// );
|
415
335
|
};
|
416
336
|
|
417
337
|
Grid.prototype.delete = function() {
|