riemann-dash 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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() {