riemann-dash 0.2.1 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/.gitignore +9 -0
  2. data/Gemfile +7 -0
  3. data/Gemfile.lock +52 -0
  4. data/README.markdown +29 -5
  5. data/Rakefile.rb +11 -0
  6. data/bin/riemann-dash +2 -2
  7. data/example/config.rb +17 -0
  8. data/lib/riemann/dash/app.rb +32 -0
  9. data/lib/riemann/dash/config.rb +154 -0
  10. data/lib/riemann/dash/controller/css.rb +1 -1
  11. data/lib/riemann/dash/controller/index.rb +6 -36
  12. data/lib/riemann/dash/public/dash.js +44 -18
  13. data/lib/riemann/dash/public/format.js +3 -3
  14. data/lib/riemann/dash/public/persistence.js +2 -2
  15. data/lib/riemann/dash/public/subs.js +63 -48
  16. data/lib/riemann/dash/public/util.js +37 -44
  17. data/lib/riemann/dash/public/vendor/backbone.js +1571 -0
  18. data/lib/riemann/dash/public/vendor/jquery/jquery-1.9.1.min.js +5 -0
  19. data/lib/riemann/dash/public/{jquery-ui-1.9.0.custom.min.js → vendor/jquery/jquery-ui-1.9.0.custom.min.js} +0 -0
  20. data/lib/riemann/dash/public/{jquery.quickfit.js → vendor/jquery/jquery.quickfit.js} +0 -0
  21. data/lib/riemann/dash/public/vendor/jquery/jquery.simplemodal.1.4.4.min.js +26 -0
  22. data/lib/riemann/dash/public/vendor/lodash.min.js +40 -0
  23. data/lib/riemann/dash/public/vendor/smoothie.js +376 -0
  24. data/lib/riemann/dash/public/{toastr.css → vendor/toastr/toastr.css} +1 -1
  25. data/lib/riemann/dash/public/{toastr.js → vendor/toastr/toastr.js} +0 -0
  26. data/lib/riemann/dash/public/views/gauge.js +8 -5
  27. data/lib/riemann/dash/public/views/grid.js +138 -67
  28. data/lib/riemann/dash/public/views/timeseries.js +230 -0
  29. data/lib/riemann/dash/public/views/title.js +6 -3
  30. data/lib/riemann/dash/version.rb +2 -2
  31. data/lib/riemann/dash/views/css.scss +52 -2
  32. data/lib/riemann/dash/views/index.erubis +38 -192
  33. data/lib/riemann/dash.rb +3 -97
  34. data/riemann-dash.gemspec +28 -0
  35. data/sh/c +1 -0
  36. data/sh/env.rb +2 -0
  37. data/sh/test +1 -0
  38. data/test/config_test.rb +114 -0
  39. data/test/fixtures/config/basic_config.rb +2 -0
  40. data/test/fixtures/config/ws_config.rb +1 -0
  41. data/test/fixtures/ws_config/dummy_config.json +1 -0
  42. data/test/fixtures/ws_config/pretty_printed_config.json +6 -0
  43. data/test/test_helper.rb +10 -0
  44. metadata +43 -18
  45. data/lib/riemann/dash/public/jquery-1.7.2.min.js +0 -4
  46. data/lib/riemann/dash/public/jquery.json-2.2.min.js +0 -31
  47. data/lib/riemann/dash/public/jquery.simplemodal.1.4.3.min.js +0 -26
  48. data/lib/riemann/dash/public/mustache.js +0 -597
  49. data/lib/riemann/dash/public/underscore-min.js +0 -5
@@ -0,0 +1,376 @@
1
+ // MIT License:
2
+ //
3
+ // Copyright (c) 2010-2011, Joe Walnes
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ // of this software and associated documentation files (the "Software"), to deal
7
+ // in the Software without restriction, including without limitation the rights
8
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ // copies of the Software, and to permit persons to whom the Software is
10
+ // furnished to do so, subject to the following conditions:
11
+ //
12
+ // The above copyright notice and this permission notice shall be included in
13
+ // all copies or substantial portions of the Software.
14
+ //
15
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ // THE SOFTWARE.
22
+
23
+ /**
24
+ * Smoothie Charts - http://smoothiecharts.org/
25
+ * (c) 2010-2012, Joe Walnes
26
+ *
27
+ * v1.0: Main charting library, by Joe Walnes
28
+ * v1.1: Auto scaling of axis, by Neil Dunn
29
+ * v1.2: fps (frames per second) option, by Mathias Petterson
30
+ * v1.3: Fix for divide by zero, by Paul Nikitochkin
31
+ * v1.4: Set minimum, top-scale padding, remove timeseries, add optional timer to reset bounds, by Kelley Reynolds
32
+ * v1.5: Set default frames per second to 50... smoother.
33
+ * .start(), .stop() methods for conserving CPU, by Dmitry Vyal
34
+ * options.interpolation = 'bezier' or 'line', by Dmitry Vyal
35
+ * options.maxValue to fix scale, by Dmitry Vyal
36
+ * v1.6: minValue/maxValue will always get converted to floats, by Przemek Matylla
37
+ * v1.7: options.grid.fillStyle may be a transparent color, by Dmitry A. Shashkin
38
+ * Smooth rescaling, by Kostas Michalopoulos
39
+ * v1.8: Set max length to customize number of live points in the dataset with options.maxDataSetLength, by Krishna Narni
40
+ * v1.9: Display timestamps along the bottom, by Nick and Stev-io
41
+ * (https://groups.google.com/forum/?fromgroups#!topic/smoothie-charts/-Ywse8FCpKI%5B1-25%5D)
42
+ * Refactored by Krishna Narni, to support timestamp formatting function
43
+ * v1.10: Switch to requestAnimationFrame, removed the now obsoleted options.fps, by Gergely Imreh
44
+ * v1.11: options.grid.sharpLines option added, by @drewnoakes
45
+ * Addressed warning seen in Firefox when seriesOption.fillStyle undefined, by @drewnoakes
46
+ */
47
+
48
+ function TimeSeries(options) {
49
+ options = options || {};
50
+ options.resetBoundsInterval = options.resetBoundsInterval || 3000; // Reset the max/min bounds after this many milliseconds
51
+ options.resetBounds = options.resetBounds === undefined ? true : options.resetBounds; // Enable or disable the resetBounds timer
52
+ this.options = options;
53
+ this.data = [];
54
+
55
+ this.maxValue = Number.NaN; // The maximum value ever seen in this time series.
56
+ this.minValue = Number.NaN; // The minimum value ever seen in this time series.
57
+
58
+ // Start a resetBounds Interval timer desired
59
+ if (options.resetBounds) {
60
+ this.boundsTimer = setInterval((function(thisObj) { return function() { thisObj.resetBounds(); } })(this), options.resetBoundsInterval);
61
+ }
62
+ }
63
+
64
+ // Reset the min and max for this timeseries so the graph rescales itself
65
+ TimeSeries.prototype.resetBounds = function() {
66
+ this.maxValue = Number.NaN;
67
+ this.minValue = Number.NaN;
68
+ for (var i = 0; i < this.data.length; i++) {
69
+ this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, this.data[i][1]) : this.data[i][1];
70
+ this.minValue = 0;
71
+ //this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, this.data[i][1]) : this.data[i][1];
72
+ }
73
+ };
74
+
75
+ TimeSeries.prototype.append = function(timestamp, value) {
76
+ this.data.push([timestamp, value]);
77
+ this.maxValue = !isNaN(this.maxValue) ? Math.max(this.maxValue, value) : value;
78
+ //this.minValue = !isNaN(this.minValue) ? Math.min(this.minValue, value) : value;
79
+ this.minValue = 0;
80
+ };
81
+
82
+ function SmoothieChart(options) {
83
+ // Defaults
84
+ options = options || {};
85
+ options.grid = options.grid || { fillStyle:'#000000', strokeStyle: '#777777', lineWidth: 1, sharpLines: false, millisPerLine: 1000, verticalSections: 2 };
86
+ options.millisPerPixel = options.millisPerPixel || 20;
87
+ options.maxValueScale = options.maxValueScale || 1;
88
+ // NOTE there are no default values for 'minValue' and 'maxValue'
89
+ options.labels = options.labels || { fillStyle:'#ffffff' };
90
+ options.interpolation = options.interpolation || "bezier";
91
+ options.scaleSmoothing = options.scaleSmoothing || 0.125;
92
+ options.maxDataSetLength = options.maxDataSetLength || 2;
93
+ options.timestampFormatter = options.timestampFormatter || null;
94
+ this.options = options;
95
+ this.seriesSet = [];
96
+ this.currentValueRange = 1;
97
+ this.currentVisMinValue = 0;
98
+ }
99
+
100
+ // Based on http://inspirit.github.com/jsfeat/js/compatibility.js
101
+ SmoothieChart.AnimateCompatibility = (function() {
102
+ var lastTime = 0,
103
+
104
+ requestAnimationFrame = function(callback, element) {
105
+ var requestAnimationFrame =
106
+ window.requestAnimationFrame ||
107
+ window.webkitRequestAnimationFrame ||
108
+ window.mozRequestAnimationFrame ||
109
+ window.oRequestAnimationFrame ||
110
+ window.msRequestAnimationFrame ||
111
+ function(callback, element) {
112
+ var currTime = new Date().getTime();
113
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
114
+ var id = window.setTimeout(function() {
115
+ callback(currTime + timeToCall);
116
+ }, timeToCall);
117
+ lastTime = currTime + timeToCall;
118
+ return id;
119
+ };
120
+ return requestAnimationFrame.call(window, callback, element);
121
+ },
122
+
123
+ cancelAnimationFrame = function(id) {
124
+ var cancelAnimationFrame =
125
+ window.cancelAnimationFrame ||
126
+ function(id) {
127
+ clearTimeout(id);
128
+ };
129
+ return cancelAnimationFrame.call(window, id);
130
+ };
131
+
132
+ return {
133
+ requestAnimationFrame: requestAnimationFrame,
134
+ cancelAnimationFrame: cancelAnimationFrame
135
+ };
136
+ })();
137
+
138
+ SmoothieChart.prototype.addTimeSeries = function(timeSeries, options) {
139
+ this.seriesSet.push({timeSeries: timeSeries, options: options || {}});
140
+ };
141
+
142
+ SmoothieChart.prototype.removeTimeSeries = function(timeSeries) {
143
+ this.seriesSet.splice(this.seriesSet.indexOf(timeSeries), 1);
144
+ };
145
+
146
+ SmoothieChart.prototype.streamTo = function(canvas, delay) {
147
+ this.canvas = canvas;
148
+ this.delay = delay;
149
+ this.start();
150
+ };
151
+
152
+ SmoothieChart.prototype.start = function() {
153
+ if (!this.frame) {
154
+ this.animate();
155
+ }
156
+ };
157
+
158
+ SmoothieChart.prototype.animate = function() {
159
+ this.frame = SmoothieChart.AnimateCompatibility.requestAnimationFrame(this.animate.bind(this));
160
+ this.render(this.canvas, new Date().getTime() - (this.delay || 0));
161
+ };
162
+
163
+ SmoothieChart.prototype.stop = function() {
164
+ if (this.frame) {
165
+ SmootheiChart.AnimateCompatibility.cancelAnimationFrame( this.frame );
166
+ delete this.frame;
167
+ }
168
+ };
169
+
170
+ // Sample timestamp formatting function
171
+ SmoothieChart.timeFormatter = function(dateObject) {
172
+ function pad2(number){return (number < 10 ? '0' : '') + number};
173
+ return pad2(dateObject.getHours())+':'+pad2(dateObject.getMinutes())+':'+pad2(dateObject.getSeconds());
174
+ };
175
+
176
+ SmoothieChart.prototype.render = function(canvas, time) {
177
+ var canvasContext = canvas.getContext("2d");
178
+ var options = this.options;
179
+ var dimensions = {top: 0, left: 0, width: canvas.clientWidth, height: canvas.clientHeight};
180
+
181
+ // Save the state of the canvas context, any transformations applied in this method
182
+ // will get removed from the stack at the end of this method when .restore() is called.
183
+ canvasContext.save();
184
+
185
+ // Round time down to pixel granularity, so motion appears smoother.
186
+ time = time - time % options.millisPerPixel;
187
+
188
+ // Move the origin.
189
+ canvasContext.translate(dimensions.left, dimensions.top);
190
+
191
+ // Create a clipped rectangle - anything we draw will be constrained to this rectangle.
192
+ // This prevents the occasional pixels from curves near the edges overrunning and creating
193
+ // screen cheese (that phrase should need no explanation).
194
+ canvasContext.beginPath();
195
+ canvasContext.rect(0, 0, dimensions.width, dimensions.height);
196
+ canvasContext.clip();
197
+
198
+ // Clear the working area.
199
+ canvasContext.save();
200
+ canvasContext.fillStyle = options.grid.fillStyle;
201
+ canvasContext.clearRect(0, 0, dimensions.width, dimensions.height);
202
+ canvasContext.fillRect(0, 0, dimensions.width, dimensions.height);
203
+ canvasContext.restore();
204
+
205
+ // Grid lines....
206
+ canvasContext.save();
207
+ canvasContext.lineWidth = options.grid.lineWidth || 1;
208
+ canvasContext.strokeStyle = options.grid.strokeStyle || '#ffffff';
209
+ // Vertical (time) dividers.
210
+ if (options.grid.millisPerLine > 0) {
211
+ for (var t = time - (time % options.grid.millisPerLine); t >= time - (dimensions.width * options.millisPerPixel); t -= options.grid.millisPerLine) {
212
+ canvasContext.beginPath();
213
+ var gx = Math.round(dimensions.width - ((time - t) / options.millisPerPixel));
214
+ if (options.grid.sharpLines)
215
+ gx -= 0.5;
216
+ canvasContext.moveTo(gx, 0);
217
+ canvasContext.lineTo(gx, dimensions.height);
218
+ canvasContext.stroke();
219
+ // To display timestamps along the bottom
220
+ // May have to adjust millisPerLine to display non-overlapping timestamps, depending on the canvas size
221
+ if (options.timestampFormatter){
222
+ var tx=new Date(t);
223
+ // Formats the timestamp based on user specified formatting function
224
+ // SmoothieChart.timeFormatter function above is one such formatting option
225
+ var ts = options.timestampFormatter(tx);
226
+ var txtwidth=(canvasContext.measureText(ts).width/2)+canvasContext.measureText(minValueString).width + 4;
227
+ if (gx<dimensions.width - txtwidth){
228
+ canvasContext.fillStyle = options.labels.fillStyle;
229
+ // Insert the time string so it doesn't overlap on the minimum value
230
+ canvasContext.fillText(ts, gx-(canvasContext.measureText(ts).width / 2), dimensions.height-2);
231
+ }
232
+ }
233
+ canvasContext.closePath();
234
+ }
235
+ }
236
+
237
+ // Horizontal (value) dividers.
238
+ for (var v = 1; v < options.grid.verticalSections; v++) {
239
+ var gy = Math.round(v * dimensions.height / options.grid.verticalSections);
240
+ if (options.grid.sharpLines)
241
+ gy -= 0.5;
242
+ canvasContext.beginPath();
243
+ canvasContext.moveTo(0, gy);
244
+ canvasContext.lineTo(dimensions.width, gy);
245
+ canvasContext.stroke();
246
+ canvasContext.closePath();
247
+ }
248
+ // Bounding rectangle.
249
+ canvasContext.beginPath();
250
+ canvasContext.strokeRect(0, 0, dimensions.width, dimensions.height);
251
+ canvasContext.closePath();
252
+ canvasContext.restore();
253
+
254
+ // Calculate the current scale of the chart, from all time series.
255
+ var maxValue = Number.NaN;
256
+ var minValue = Number.NaN;
257
+
258
+ for (var d = 0; d < this.seriesSet.length; d++) {
259
+ // TODO(ndunn): We could calculate / track these values as they stream in.
260
+ var timeSeries = this.seriesSet[d].timeSeries;
261
+ if (!isNaN(timeSeries.maxValue)) {
262
+ maxValue = !isNaN(maxValue) ? Math.max(maxValue, timeSeries.maxValue) : timeSeries.maxValue;
263
+ }
264
+
265
+ if (!isNaN(timeSeries.minValue)) {
266
+ minValue = !isNaN(minValue) ? Math.min(minValue, timeSeries.minValue) : timeSeries.minValue;
267
+ }
268
+ }
269
+
270
+ if (isNaN(maxValue) && isNaN(minValue)) {
271
+ canvasContext.restore(); // without this there is crash in Android browser
272
+ return;
273
+ }
274
+
275
+ // Scale the maxValue to add padding at the top if required
276
+ if (options.maxValue != null)
277
+ maxValue = options.maxValue;
278
+ else
279
+ maxValue = maxValue * options.maxValueScale;
280
+ // Set the minimum if we've specified one
281
+ if (options.minValue != null)
282
+ minValue = options.minValue;
283
+ var targetValueRange = maxValue - minValue;
284
+ this.currentValueRange += options.scaleSmoothing*(targetValueRange - this.currentValueRange);
285
+ this.currentVisMinValue += options.scaleSmoothing*(minValue - this.currentVisMinValue);
286
+ var valueRange = this.currentValueRange;
287
+ var visMinValue = this.currentVisMinValue;
288
+
289
+ // For each data set...
290
+ for (var d = 0; d < this.seriesSet.length; d++) {
291
+ canvasContext.save();
292
+ var timeSeries = this.seriesSet[d].timeSeries;
293
+ var dataSet = timeSeries.data;
294
+ var seriesOptions = this.seriesSet[d].options;
295
+
296
+ // Delete old data that's moved off the left of the chart.
297
+ // We must always keep the last expired data point as we need this to draw the
298
+ // line that comes into the chart, but any points prior to that can be removed.
299
+ while (dataSet.length >= options.maxDataSetLength && dataSet[1][0] < time - (dimensions.width * options.millisPerPixel)) {
300
+ dataSet.splice(0, 1);
301
+ }
302
+
303
+ // Set style for this dataSet.
304
+ canvasContext.lineWidth = seriesOptions.lineWidth || 1;
305
+ canvasContext.strokeStyle = seriesOptions.strokeStyle || '#ffffff';
306
+ // Draw the line...
307
+ canvasContext.beginPath();
308
+ // Retain lastX, lastY for calculating the control points of bezier curves.
309
+ var firstX = 0, lastX = 0, lastY = 0;
310
+ for (var i = 0; i < dataSet.length; i++) {
311
+ // TODO: Deal with dataSet.length < 2.
312
+ var x = Math.round(dimensions.width - ((time - dataSet[i][0]) / options.millisPerPixel));
313
+ var value = dataSet[i][1];
314
+ var offset = value - visMinValue;
315
+ var scaledValue = dimensions.height - (valueRange ? Math.round((offset / valueRange) * dimensions.height) : 0);
316
+ var y = Math.max(Math.min(scaledValue, dimensions.height - 1), 1); // Ensure line is always on chart.
317
+
318
+ if (i == 0) {
319
+ firstX = x;
320
+ canvasContext.moveTo(x, y);
321
+ }
322
+ // Great explanation of Bezier curves: http://en.wikipedia.org/wiki/Bezier_curve#Quadratic_curves
323
+ //
324
+ // Assuming A was the last point in the line plotted and B is the new point,
325
+ // we draw a curve with control points P and Q as below.
326
+ //
327
+ // A---P
328
+ // |
329
+ // |
330
+ // |
331
+ // Q---B
332
+ //
333
+ // Importantly, A and P are at the same y coordinate, as are B and Q. This is
334
+ // so adjacent curves appear to flow as one.
335
+ //
336
+ else {
337
+ switch (options.interpolation) {
338
+ case "line":
339
+ canvasContext.lineTo(x,y);
340
+ break;
341
+ case "bezier":
342
+ default:
343
+ canvasContext.bezierCurveTo( // startPoint (A) is implicit from last iteration of loop
344
+ Math.round((lastX + x) / 2), lastY, // controlPoint1 (P)
345
+ Math.round((lastX + x)) / 2, y, // controlPoint2 (Q)
346
+ x, y); // endPoint (B)
347
+ break;
348
+ }
349
+ }
350
+
351
+ lastX = x; lastY = y;
352
+ }
353
+ if (dataSet.length > 0 && seriesOptions.fillStyle) {
354
+ // Close up the fill region.
355
+ canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, lastY);
356
+ canvasContext.lineTo(dimensions.width + seriesOptions.lineWidth + 1, dimensions.height + seriesOptions.lineWidth + 1);
357
+ canvasContext.lineTo(firstX, dimensions.height + seriesOptions.lineWidth);
358
+ canvasContext.fillStyle = seriesOptions.fillStyle;
359
+ canvasContext.fill();
360
+ }
361
+ canvasContext.stroke();
362
+ canvasContext.closePath();
363
+ canvasContext.restore();
364
+ }
365
+
366
+ // Draw the axis values on the chart.
367
+ if (!options.labels.disabled) {
368
+ canvasContext.fillStyle = options.labels.fillStyle;
369
+ var maxValueString = parseFloat(maxValue).toFixed(2);
370
+ var minValueString = parseFloat(minValue).toFixed(2);
371
+ canvasContext.fillText(maxValueString, dimensions.width - canvasContext.measureText(maxValueString).width - 2, 10);
372
+ canvasContext.fillText(minValueString, dimensions.width - canvasContext.measureText(minValueString).width - 2, dimensions.height - 2);
373
+ }
374
+
375
+ canvasContext.restore(); // See .save() above.
376
+ };
@@ -92,7 +92,7 @@
92
92
  .toast-top-right
93
93
  {
94
94
  right: 12px;
95
- top: 12px;
95
+ top: 38px;
96
96
  }
97
97
 
98
98
  #toast-container > :hover
@@ -49,12 +49,15 @@
49
49
  });
50
50
  }
51
51
 
52
+ var editTemplate = _.template(
53
+ "<label for='title'>Title</label>" +
54
+ "<input type='text' name='title' value='{{title}}' /><br />" +
55
+ "<label for='query'>Query</label>" +
56
+ "<input type='text' name='query' value='{{query}}' />"
57
+ )
58
+
52
59
  Gauge.prototype.editForm = function() {
53
- return Mustache.render('<label for="title">Title</label>' +
54
- '<input type="text" name="title" value="{{title}}" /><br />' +
55
- '<label for="query">Query</label>' +
56
- '<input type="text" name="query" value="{{query}}" />',
57
- this)
60
+ return editTemplate(this);
58
61
  }
59
62
 
60
63
  Gauge.prototype.reflow = function() {