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.
@@ -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(200, this.render);
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.row_fn = extract_fn(json.rows) || extract_fn('host');
54
- this.col_fn = extract_fn(json.cols) || extract_fn('service');
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(this);
117
- };
118
-
119
- // Returns all events, flat.
120
- Grid.prototype.allEvents = function() {
121
- var events = [];
122
- for (var row in this.events) {
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
- var max_key = this.max_fn(event);
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
- // Generates a td cell for an event. Returns a map of the td, bar, and metric
142
- // elements.
143
- Grid.prototype.newTdCache = function() {
144
- var td = $('<td><span class="bar"><span class="metric"/></span></td>');
145
- var bar = td.find('.bar');
146
- var metric = td.find('.metric');
147
- return {td: td, bar: bar, metric: metric};
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
- // Update a single jq element with information about an event.
151
- Grid.prototype.renderElement = function(e, event) {
204
+ // Returns a td for the given element.
205
+ Grid.prototype.renderElement = function(event) {
152
206
  if (event === undefined) {
153
207
  // Nuke element
154
- e.metric.text('');
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
- e.td.attr('class', "state box " + event.state);
219
+ td.attr('class', "state box " + event.state);
163
220
 
164
221
  // Description
165
- e.td.attr('title', event.host + ' ' + event.service + "\n" + event.state +
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
- e.metric.text(format.float(event.metric));
229
+ metric.text(format.float(event.metric));
173
230
  } else if (event.state != undefined) {
174
- e.metric.text(event.state);
231
+ metric.text(event.state);
175
232
  }
176
233
 
177
234
  // Bar chart
178
- if (event.metric === 0) {
179
- // Zero
180
- e.bar.css('width', 0);
181
- } else if (0 < event.metric) {
235
+ if (event.metric === null ||
236
+ event.metric === undefined ||
237
+ event.metric <= 0) {
238
+ bar.css('width', 0);
239
+ } else {
182
240
  // Positive
183
- e.bar.css('width',
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
- this.renderElement(cache, event);
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
- // Compute short names.
223
- var colPrefix = strings.commonPrefix(this.cols);
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(name, i) {
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(subName) {
254
- var event = this.events[name][subName];
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() {