bp-fnordmetric 1.2.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/Gemfile +6 -0
  2. data/Rakefile +6 -0
  3. data/fnordmetric.gemspec +41 -0
  4. data/lib/fnordmetric.rb +149 -0
  5. data/lib/fnordmetric/acceptors/acceptor.rb +42 -0
  6. data/lib/fnordmetric/acceptors/amqp_acceptor.rb +56 -0
  7. data/lib/fnordmetric/acceptors/fyrehose_acceptor.rb +43 -0
  8. data/lib/fnordmetric/acceptors/stomp_acceptor.rb +71 -0
  9. data/lib/fnordmetric/acceptors/tcp_acceptor.rb +58 -0
  10. data/lib/fnordmetric/acceptors/udp_acceptor.rb +37 -0
  11. data/lib/fnordmetric/api.rb +46 -0
  12. data/lib/fnordmetric/cache.rb +20 -0
  13. data/lib/fnordmetric/context.rb +96 -0
  14. data/lib/fnordmetric/defaults.rb +22 -0
  15. data/lib/fnordmetric/enterprise/compatibility_handler.rb +42 -0
  16. data/lib/fnordmetric/ext.rb +75 -0
  17. data/lib/fnordmetric/gauge.rb +98 -0
  18. data/lib/fnordmetric/gauge_calculations.rb +106 -0
  19. data/lib/fnordmetric/gauge_modifiers.rb +144 -0
  20. data/lib/fnordmetric/gauge_rendering.rb +40 -0
  21. data/lib/fnordmetric/gauge_validations.rb +15 -0
  22. data/lib/fnordmetric/gauges/distribution_gauge.rb +87 -0
  23. data/lib/fnordmetric/gauges/server_health_gauge.rb +13 -0
  24. data/lib/fnordmetric/gauges/timeseries_gauge.rb +138 -0
  25. data/lib/fnordmetric/gauges/toplist_gauge.rb +44 -0
  26. data/lib/fnordmetric/histogram.rb +64 -0
  27. data/lib/fnordmetric/logger.rb +63 -0
  28. data/lib/fnordmetric/namespace.rb +208 -0
  29. data/lib/fnordmetric/session.rb +139 -0
  30. data/lib/fnordmetric/standalone.rb +20 -0
  31. data/lib/fnordmetric/timeseries.rb +79 -0
  32. data/lib/fnordmetric/toplist.rb +61 -0
  33. data/lib/fnordmetric/udp_client.rb +22 -0
  34. data/lib/fnordmetric/util.rb +25 -0
  35. data/lib/fnordmetric/version.rb +3 -0
  36. data/lib/fnordmetric/web/app.rb +63 -0
  37. data/lib/fnordmetric/web/app_helpers.rb +42 -0
  38. data/lib/fnordmetric/web/dashboard.rb +40 -0
  39. data/lib/fnordmetric/web/event.rb +99 -0
  40. data/lib/fnordmetric/web/reactor.rb +127 -0
  41. data/lib/fnordmetric/web/web.rb +59 -0
  42. data/lib/fnordmetric/web/websocket.rb +41 -0
  43. data/lib/fnordmetric/widget.rb +82 -0
  44. data/lib/fnordmetric/widgets/bars_widget.rb +44 -0
  45. data/lib/fnordmetric/widgets/html_widget.rb +28 -0
  46. data/lib/fnordmetric/widgets/numbers_widget.rb +80 -0
  47. data/lib/fnordmetric/widgets/pie_widget.rb +23 -0
  48. data/lib/fnordmetric/widgets/timeseries_widget.rb +65 -0
  49. data/lib/fnordmetric/widgets/toplist_widget.rb +68 -0
  50. data/lib/fnordmetric/worker.rb +89 -0
  51. data/lib/fnordmetric/zero_config_gauge.rb +138 -0
  52. data/run_specs.sh +11 -0
  53. data/spec/api_spec.rb +49 -0
  54. data/spec/context_spec.rb +42 -0
  55. data/spec/dashboard_spec.rb +38 -0
  56. data/spec/event_spec.rb +170 -0
  57. data/spec/ext_spec.rb +14 -0
  58. data/spec/fnordmetric_spec.rb +56 -0
  59. data/spec/gauge_like_shared.rb +56 -0
  60. data/spec/gauge_modifiers_spec.rb +583 -0
  61. data/spec/gauge_spec.rb +230 -0
  62. data/spec/namespace_spec.rb +114 -0
  63. data/spec/session_spec.rb +231 -0
  64. data/spec/spec_helper.rb +49 -0
  65. data/spec/tcp_acceptor_spec.rb +35 -0
  66. data/spec/timeseries_gauge_spec.rb +56 -0
  67. data/spec/udp_acceptor_spec.rb +35 -0
  68. data/spec/util_spec.rb +46 -0
  69. data/spec/widget_spec.rb +113 -0
  70. data/spec/worker_spec.rb +40 -0
  71. data/web/.gitignore +4 -0
  72. data/web/build.sh +34 -0
  73. data/web/css/fnordmetric.core.css +868 -0
  74. data/web/haml/app.haml +20 -0
  75. data/web/haml/distribution_gauge.haml +118 -0
  76. data/web/haml/timeseries_gauge.haml +80 -0
  77. data/web/haml/toplist_gauge.haml +194 -0
  78. data/web/img/head.png +0 -0
  79. data/web/img/list.png +0 -0
  80. data/web/img/list_active.png +0 -0
  81. data/web/img/list_hover.png +0 -0
  82. data/web/img/loader.gif +0 -0
  83. data/web/img/loader_white.gif +0 -0
  84. data/web/img/navbar.png +0 -0
  85. data/web/img/navbar_btn.png +0 -0
  86. data/web/img/picto_gauge.png +0 -0
  87. data/web/js/fnordmetric.bars_widget.js +178 -0
  88. data/web/js/fnordmetric.dashboard_view.js +99 -0
  89. data/web/js/fnordmetric.gauge_explorer.js +173 -0
  90. data/web/js/fnordmetric.gauge_view.js +260 -0
  91. data/web/js/fnordmetric.html_widget.js +21 -0
  92. data/web/js/fnordmetric.js +315 -0
  93. data/web/js/fnordmetric.numbers_widget.js +122 -0
  94. data/web/js/fnordmetric.overview_view.js +35 -0
  95. data/web/js/fnordmetric.pie_widget.js +118 -0
  96. data/web/js/fnordmetric.realtime_timeline_widget.js +175 -0
  97. data/web/js/fnordmetric.session_view.js +342 -0
  98. data/web/js/fnordmetric.timeline_widget.js +333 -0
  99. data/web/js/fnordmetric.timeseries_widget.js +405 -0
  100. data/web/js/fnordmetric.toplist_widget.js +119 -0
  101. data/web/js/fnordmetric.ui.js +91 -0
  102. data/web/js/fnordmetric.util.js +248 -0
  103. data/web/vendor/font-awesome/css/font-awesome-ie7.min.css +22 -0
  104. data/web/vendor/font-awesome/css/font-awesome.css +540 -0
  105. data/web/vendor/font-awesome/css/font-awesome.min.css +33 -0
  106. data/web/vendor/font-awesome/font/FontAwesome.otf +0 -0
  107. data/web/vendor/font-awesome/font/fontawesome-webfont.eot +0 -0
  108. data/web/vendor/font-awesome/font/fontawesome-webfont.svg +284 -0
  109. data/web/vendor/font-awesome/font/fontawesome-webfont.ttf +0 -0
  110. data/web/vendor/font-awesome/font/fontawesome-webfont.woff +0 -0
  111. data/web/vendor/jquery-1.6.2.min.js +18 -0
  112. data/web/vendor/jquery-ui.min.js +6 -0
  113. data/web/vendor/jquery.combobox.js +129 -0
  114. data/web/vendor/jquery.maskedinput.js +252 -0
  115. metadata +438 -0
@@ -0,0 +1,333 @@
1
+ FnordMetric.widgets.timelineWidget = function(){
2
+
3
+ var widget_uid = FnordMetric.util.getNextWidgetUID();
4
+ var width, height, canvas, series, opts, xtick;
5
+ var xticks = 100;
6
+ var running_request = false;
7
+
8
+ var series_paths = {};
9
+ var series_values = {};
10
+
11
+ var xpadding = 30;
12
+
13
+ function render(_opts){
14
+ opts = _opts;
15
+ //if(!silent){ $(opts.elem).css('opacity', 0.5); }
16
+
17
+ if(opts.xticks){ xticks = opts.xticks; }
18
+ if(!opts.draw_points){ opts.draw_points = true; }
19
+ if(!opts.draw_path){ opts.draw_path = true; }
20
+ if(!opts.draw_ygrid){ opts.draw_ygrid = true; }
21
+ if(!opts.draw_xgrid){ opts.draw_xgrid = true; }
22
+
23
+ drawLayout(opts);
24
+
25
+ width = opts.elem.width() - (xpadding * 2) - 15;
26
+ height = opts.height || 240;
27
+ xtick = width / (xticks - 1);
28
+
29
+ canvas = d3.select('#container-'+widget_uid)
30
+ .append("svg:svg")
31
+ .attr("width", width+(2*xpadding))
32
+ .attr("height", height+30);
33
+
34
+ canvas.selectAll("*").remove();
35
+
36
+ for (ind in opts.series){
37
+ series_values[opts.series[ind]] = {};
38
+ }
39
+
40
+ updateRange();
41
+ updateChart();
42
+
43
+ if(opts.autoupdate){
44
+ var secs = parseInt(opts.autoupdate);
45
+ if(secs > 0){
46
+ var autoupdate_interval = window.setInterval(function(){
47
+ updateRange(true); updateChart(); // FIXPAUL: only update if not scrolled
48
+ }, secs*1000);
49
+
50
+ $('body').bind('fm_dashboard_close', function(){
51
+ window.clearInterval(autoupdate_interval);
52
+ });
53
+ }
54
+ }
55
+
56
+ //canvas.drawGrid(0, 0, width+(2*xpadding), height, 1, 6, "#ececec");
57
+ }
58
+
59
+ function announce(evt){
60
+ if(evt.widget_key == opts.widget_key){
61
+ if((evt.type == "widget_response") && (evt.cmd == "values_at")){
62
+ running_request = false;
63
+ updateSeriesData(evt.tick, evt.values);
64
+ updateChart();
65
+ }
66
+ }
67
+ }
68
+
69
+ function requestValues(_tick, times){
70
+ if(times.length > 0){
71
+ if(!running_request){
72
+ running_request = (new Date).getTime();
73
+ requestValuesAsync(_tick, times);
74
+ }
75
+ }
76
+ }
77
+
78
+ function requestValuesAsync(_tick, times){
79
+ FnordMetric.publish({
80
+ "_class": "widget_request",
81
+ "_channel": opts.channel,
82
+ "cmd": "values_at",
83
+ "tick": _tick,
84
+ "ticks": times,
85
+ "widget_key": opts.widget_key
86
+ })
87
+ }
88
+
89
+
90
+ function changeTick(){
91
+ opts.tick = parseInt($(this).attr('data-tick'));
92
+ opts.start_timestamp = null;
93
+ opts.end_timestamp = null;
94
+ updateRange();
95
+ redrawDatepicker();
96
+ updateChart();
97
+ }
98
+
99
+ function updateSeriesData(_tick, values){
100
+ for(_series in values){
101
+ for(_t in values[_series]){
102
+ series_values[_series][_tick+"+"+_t] = values[_series][_t];
103
+ }
104
+ }
105
+ }
106
+
107
+ function updateChart(){
108
+ var _ticks = [];
109
+ var _miss = [];
110
+ var _max = [];
111
+ var _rndr = [];
112
+
113
+ for(sind in opts.series){
114
+ var _last = opts.start_timestamp + opts.tick;
115
+ var _delta = (opts.end_timestamp - _last) / xticks;
116
+ var _sdata = [];
117
+
118
+ for(var n=0; n < xticks; n++){
119
+ var _t = (parseInt(_last / opts.tick) * opts.tick);
120
+ var _v = series_values[opts.series[sind]][opts.tick+"+"+_t];
121
+ if((_v === undefined) && (_miss.indexOf(_t) == -1)){ _miss.push(_t); }
122
+ _sdata.push(_v || 0);
123
+ _last += _delta;
124
+ }
125
+
126
+ _max.push(Math.max.apply(Math, _sdata));
127
+ _rndr.push([opts.series[sind], _sdata]);
128
+ }
129
+
130
+ _max = Math.max.apply(Math, _max)*1.1;
131
+ if(_max == 0){ _max = 1; }
132
+
133
+ for(rind in _rndr){
134
+ drawSeries(_rndr[rind][0], _rndr[rind][1], _max);
135
+ }
136
+
137
+ if(_miss.length > 0)
138
+ requestValues(opts.tick, _miss);
139
+
140
+ redrawDatepicker();
141
+ }
142
+
143
+ function refreshChart(){
144
+ for(_series in series_values){
145
+ series_values[_series] = {};
146
+ }
147
+
148
+ updateRange();
149
+ updateChart();
150
+ }
151
+
152
+ function drawSeries(series, series_data, _max){
153
+
154
+ //var path_string = "M0,"+height;
155
+ var path_string = "";
156
+ var _color = '0066CC';
157
+
158
+ if (series_paths[series]){
159
+ for(ind in series_paths[series]){
160
+ series_paths[series][ind].remove();
161
+ }
162
+ }
163
+
164
+ series_paths[series] = [];
165
+
166
+ if(!_max){ _max = Math.max.apply(Math, series_data)*1.1; }
167
+ if(_max < 1){ _max = 1; }
168
+
169
+ $(series_data).each(function(i,v){
170
+
171
+ var p_x = xpadding+(i*xtick);
172
+ var p_y = (height-((v/_max)*height));
173
+
174
+ path_string += ( ( i == 0 ? "M" : "L" ) + p_x + ',' + p_y );
175
+
176
+ // if(i%label_mod==0){
177
+ // canvas.text(p_x, height+10, labels[i]).attr({
178
+ // font: '10px Helvetica, Arial',
179
+ // fill: "#777"
180
+ // });
181
+ // }
182
+
183
+ if(opts.draw_points){
184
+ series_paths[series].push(canvas
185
+ .append("svg:circle")
186
+ .attr("cx", p_x)
187
+ .attr("cy", p_y)
188
+ .attr("stroke-width", "1px")
189
+ .attr("stroke", "#fff")
190
+ .attr("fill", _color)
191
+ .attr("fill-opacity", 1)
192
+ .attr("r", 4)
193
+ );
194
+ }
195
+
196
+ // var htrgt = canvas.rect(p_x - 20, p_y - 20, 40, 40).attr({
197
+ // stroke: "none",
198
+ // fill: "#fff",
199
+ // opacity: 0
200
+ // }).toFront();
201
+
202
+ //series_paths[series].push(htrgt);
203
+
204
+ // (function(htrgt, series_paths){
205
+
206
+ // var t_y = p_y + 9;
207
+ // var ttt = canvas.text(p_x, t_y+10, v).attr({
208
+ // font: '12px Helvetica, Arial',
209
+ // fill: "#fff",
210
+ // opacity: 0
211
+ // });
212
+
213
+ // var tttb = ttt.getBBox();
214
+ // var ttw = tttb.width+20;
215
+ // var tt = canvas.rect(p_x-(ttw/2), t_y, ttw, 22, 5).attr({
216
+ // stroke: "none",
217
+ // fill: "#000",
218
+ // opacity: 0
219
+ // }).toBack();
220
+
221
+ // series_paths[series].push(tt);
222
+ // series_paths[series].push(ttt);
223
+
224
+ // $(htrgt[0]).hover(function(){
225
+ // tt.animate({ opacity: 0.8 }, 300);
226
+ // ttt.animate({ opacity: 0.8 }, 300);
227
+ // }, function(){
228
+ // tt.animate({ opacity: 0 }, 300);
229
+ // ttt.animate({ opacity: 0 }, 300);
230
+ // });
231
+
232
+ // })(htrgt, series_paths);
233
+
234
+ });
235
+
236
+ if(opts.draw_path){
237
+ series_paths[series].push(canvas.append("svg:path")
238
+ .attr("fill", "none")
239
+ .attr("stroke", "steelblue")
240
+ .attr("stroke-width", 3)
241
+ .attr("d", path_string)
242
+ );
243
+ }
244
+
245
+ if(opts.draw_area){
246
+ // path_string += "L"+(width+xpadding)+","+height+" L"+xpadding+","+height+" Z";
247
+
248
+ // series_paths[series].push(
249
+ // canvas.path(path_string).attr({
250
+ // stroke: "none",
251
+ // fill: _color,
252
+ // opacity: 0.1
253
+ // }).toBack()
254
+ // );
255
+ }
256
+ }
257
+
258
+ function drawLayout(opts){
259
+ $(opts.elem).append( $('<div></div>').attr('class', 'headbar').append(
260
+ $('<div></div>').attr('class', 'button mr').append($('<span></span>').html('refresh')).click(
261
+ function(){ refreshChart(); }
262
+ )
263
+ ).append(
264
+ $('<div></div>').attr('class', 'button mr').append($('<span></span>').html('&rarr;')).click(
265
+ function(){ moveRange(1); }
266
+ )
267
+ ).append(
268
+ $('<div></div>').attr('class', 'datepicker')
269
+ ).append(
270
+ $('<div></div>').attr('class', 'button ml').append($('<span></span>').html('&larr;')).click(
271
+ function(){ moveRange(-1); }
272
+ )
273
+ ).append(
274
+ $('<h2></h2>').html(opts.title)
275
+ ) ).append(
276
+ $('<div></div>').attr('id', 'container-'+widget_uid).css({
277
+ height: opts.height + 6,
278
+ marginBottom: 20,
279
+ overflow: 'hidden'
280
+ })
281
+ );
282
+
283
+ if(opts.ticks){
284
+ $('.headbar', opts.elem).append('<div class="tick_btns btn_group"></div>');
285
+ for(__tick in opts.ticks){
286
+ var _tick = opts.ticks[__tick];
287
+ $('.tick_btns', opts.elem).append(
288
+ $('<div></div>').attr('class', 'button tick').append($('<span></span>')
289
+ .html(FnordMetric.util.formatTimeRange(_tick)))
290
+ .attr('data-tick', _tick)
291
+ .click(changeTick)
292
+ );
293
+ }
294
+ }
295
+ }
296
+
297
+ function redrawDatepicker(){
298
+ $('.datepicker', opts.elem).html(
299
+ Highcharts.dateFormat('%d.%m.%y %H:%M', parseInt(opts.start_timestamp)*1000) +
300
+ '&nbsp;&dash;&nbsp;' +
301
+ Highcharts.dateFormat('%d.%m.%y %H:%M', parseInt(opts.end_timestamp)*1000)
302
+ );
303
+ }
304
+
305
+ function updateRange(force){
306
+ if(!opts.tick){
307
+ opts.tick = opts.ticks[0];
308
+ }
309
+
310
+ if(!opts.start_timestamp || !opts.end_timestamp || !!force){
311
+ opts.end_timestamp = parseInt(new Date().getTime() / 1000);
312
+ opts.start_timestamp = opts.end_timestamp - (opts.tick * xticks);
313
+ }
314
+ }
315
+
316
+ function moveRange(direction){
317
+ v = opts.tick*direction*8;
318
+
319
+ if(((opts.end_timestamp + v)*1000) < new Date().getTime()){
320
+ opts.start_timestamp += v;
321
+ opts.end_timestamp += v;
322
+ }
323
+
324
+ updateChart();
325
+ }
326
+
327
+
328
+ return {
329
+ render: render,
330
+ announce: announce
331
+ }
332
+
333
+ };
@@ -0,0 +1,405 @@
1
+ FnordMetric.widgets.timeseriesWidget = function(){
2
+
3
+ /*
4
+ options:
5
+ height: 430
6
+ default_cardinal: true/false
7
+ default_style: line/area/stack/flow
8
+ series
9
+ */
10
+
11
+ var widget_uid = "fnord-" + parseInt(Math.random()*99990000);
12
+ var width, height, opts, graph, gconfig, legend, hoverDetail, shelving, highlighter, resolution;
13
+
14
+ var cardinal = true;
15
+ var xticks = 30;
16
+
17
+ function render(_opts){
18
+ opts = _opts;
19
+
20
+ if(opts.default_style == "areaspline"){ opts.default_style = 'area'; }
21
+ if(!opts.default_style){ opts.default_style = 'line'; }
22
+ if(!opts.default_cardinal){ opts.default_cardinal = false; }
23
+ if(opts.xticks){ xticks = opts.xticks; }
24
+
25
+ draw_layout();
26
+
27
+ gconfig = {
28
+ element: $('.container', opts.elem)[0],
29
+ interpolation: (opts.default_cardinal ? 'cardinal' : 'linear'),
30
+ stroke: true,
31
+ series: opts.series,
32
+ padding: { top: 0.1, bottom: 0 }
33
+ }
34
+
35
+ $(opts.elem)
36
+ .addClass('resize_listener')
37
+ .bind('fm_resize', renderChart);
38
+
39
+ apply_style(opts.default_style);
40
+
41
+ if(opts.async_chart){
42
+ updateChart();
43
+ } else {
44
+ renderChart();
45
+ }
46
+ }
47
+
48
+ function toggle_cardinal(){
49
+ if($(this).hasClass('active')){ return false; }
50
+ $(this).addClass('active').siblings().removeClass('active');
51
+
52
+ if(cardinal === true){
53
+ cardinal = false;
54
+ gconfig.interpolation = 'linear';
55
+ } else {
56
+ cardinal = true;
57
+ gconfig.interpolation = 'cardinal';
58
+ }
59
+
60
+ graph.configure(gconfig);
61
+ graph.render();
62
+ }
63
+
64
+ function change_style(){
65
+ $(this).addClass('active').siblings().removeClass('active');
66
+
67
+ apply_style($(this).attr('data'));
68
+ //graph.configure(gconfig);
69
+ //graph.render();
70
+
71
+ renderChart();
72
+ }
73
+
74
+ function apply_style(style){
75
+ if(style == 'line'){
76
+ gconfig.renderer = 'line';
77
+ gconfig.offset = 'zero';
78
+ }
79
+
80
+ if(style == 'area'){
81
+ gconfig.renderer = 'area';
82
+ gconfig.offset = 'zero';
83
+ }
84
+
85
+ if(style == 'flow'){
86
+ gconfig.renderer = 'stack';
87
+ gconfig.offset = 'silhouette';
88
+ }
89
+ }
90
+
91
+ function apply_resolution(){
92
+ if(!resolution){
93
+ var trgt_resolution = 50;
94
+ var best_resolution = false;
95
+
96
+ for(ind in opts.series_resolutions){
97
+ var _diff = Math.abs(
98
+ trgt_resolution -
99
+ (opts.timespan / opts.series_resolutions[ind])
100
+ );
101
+
102
+ if((!best_resolution) || (_diff < best_resolution)){
103
+ best_resolution = opts.series_resolutions[ind];
104
+ }
105
+ }
106
+
107
+ resolution = best_resolution;
108
+ }
109
+
110
+ for(ind in gconfig.series){
111
+ gconfig.series[ind].data = gconfig.series[ind]["data"+resolution];
112
+ }
113
+
114
+ $('.button.tick', opts.elem)
115
+ .removeClass('active')
116
+ .filter('[data-tick="'+resolution+'"]')
117
+ .addClass('active');
118
+ }
119
+
120
+ function draw_layout(){
121
+ if(!opts.ext && !opts.no_headbar){
122
+ $(opts.elem).append(
123
+ $('<div></div>')
124
+ .addClass('headbar')
125
+ .append($('<h2></h2>').html(opts.title))
126
+ .append(
127
+ $('<div class="btn_group mr"></div>')
128
+ .append(
129
+ $('<div></div>')
130
+ .addClass('button')
131
+ .append($('<span>').html('Flow'))
132
+ .attr('data', 'flow')
133
+ .click(change_style)
134
+ )
135
+ .append(
136
+ $('<div></div>')
137
+ .addClass('button')
138
+ .append($('<span>').html('Area'))
139
+ .attr('data', 'area')
140
+ .click(change_style)
141
+ )
142
+ .append(
143
+ $('<div></div>')
144
+ .addClass('button')
145
+ .append($('<span>').html('Line'))
146
+ .attr('data', 'line')
147
+ .click(change_style)
148
+ )
149
+ )
150
+ .append(
151
+ $('<div class="btn_group mr"></div>')
152
+ .append(
153
+ $('<div></div>')
154
+ .addClass('button mr')
155
+ .addClass(opts.default_cardinal ? '' : 'active')
156
+ .append($('<span>').html('Off'))
157
+ .attr('data', 'cardinal-off')
158
+ .click(toggle_cardinal)
159
+ )
160
+ .append(
161
+ $('<div></div>')
162
+ .addClass('button ml')
163
+ .addClass(opts.default_cardinal ? 'active' : '')
164
+ .append($('<span>').html('On'))
165
+ .attr('data', 'cardinal-on')
166
+ .click(toggle_cardinal)
167
+ )
168
+ )
169
+ )
170
+ }
171
+
172
+ $(opts.elem)
173
+ .append(
174
+ $('<div></div>')
175
+ .addClass('legend')
176
+ .css({
177
+ margin: '10px 30px 0 30px',
178
+ })
179
+ )
180
+ .append(
181
+ $('<div></div>')
182
+ .addClass('container')
183
+ .css({
184
+ height: opts.height,
185
+ margin: '0 23px 25px 23px',
186
+ })
187
+ );
188
+
189
+ if(!opts.ext && opts.async_chart && !opts.no_datepicker){
190
+ $('.headbar', opts.elem).prepend(
191
+ $('<div></div>')
192
+ .addClass('button ml')
193
+ .append($('<span></span>').html('&larr;'))
194
+ .click(function(){ moveRange(-1); })
195
+ ).prepend(
196
+ $('<div></div>')
197
+ .addClass('datepicker')
198
+ ).prepend(
199
+ $('<div></div>')
200
+ .addClass('button')
201
+ .append($('<span></span>').html('&rarr;'))
202
+ .click(function(){ moveRange(1); })
203
+ ).prepend(
204
+ $('<div class="refresh_btn"></div>')
205
+ .addClass('button ml')
206
+ .append($('<span></span>').html('refresh'))
207
+ .click(function(){ requestValuesAsync(); })
208
+ );
209
+ }
210
+
211
+ if(opts.series_resolutions){
212
+ $('.headbar', opts.elem).append('<div class="tick_btns btn_group"></div>');
213
+ for(ind in opts.series_resolutions){
214
+ var _tick = opts.series_resolutions[ind];
215
+ $('.tick_btns', opts.elem).append(
216
+ $('<div></div>').attr('class', 'button tick').append($('<span></span>')
217
+ .html(FnordMetric.util.formatTimeRange(_tick)))
218
+ .attr('data-tick', _tick)
219
+ .click(function(){
220
+ resolution = $(this).attr('data-tick');
221
+ renderChart();
222
+ })
223
+ );
224
+ }
225
+ }
226
+
227
+ if((opts.autoupdate) && (opts.async_chart)){
228
+ var secs = parseInt(opts.autoupdate);
229
+ if(secs > 0){
230
+ var autoupdate_interval = window.setInterval(function(){
231
+ updateChart(false, true);
232
+ }, secs*1000);
233
+
234
+ $('body').bind('fm_dashboard_close', function(){
235
+ window.clearInterval(autoupdate_interval);
236
+ });
237
+ }
238
+ }
239
+ }
240
+
241
+ function renderChart(){
242
+ width = opts.elem.width() - 50;
243
+ height = opts.height || 240;
244
+
245
+ gconfig.width = width;
246
+ gconfig.height = height;
247
+
248
+ $(gconfig.element).html("");
249
+ $(".fnordmetric_legend", opts.elem).html("");
250
+
251
+ if(opts.series_resolutions){
252
+ apply_resolution();
253
+ }
254
+
255
+ graph = new FnordMetric.rickshaw.Graph(gconfig);
256
+
257
+ legend = new FnordMetric.rickshaw.Graph.Legend({
258
+ graph: graph,
259
+ element: $('.legend', opts.elem)[0]
260
+ });
261
+
262
+ hoverDetail = new FnordMetric.rickshaw.Graph.HoverDetail( {
263
+ graph: graph
264
+ });
265
+
266
+ shelving = new FnordMetric.rickshaw.Graph.Behavior.Series.Toggle({
267
+ graph: graph,
268
+ legend: legend
269
+ });
270
+
271
+ highlighter = new FnordMetric.rickshaw.Graph.Behavior.Series.Highlight({
272
+ graph: graph,
273
+ legend: legend
274
+ });
275
+
276
+ new FnordMetric.rickshaw.Graph.Axis.Time({
277
+ graph: graph,
278
+ }).render();
279
+
280
+ new FnordMetric.rickshaw.Graph.Axis.Y({
281
+ graph: graph,
282
+ }).render();
283
+
284
+ if(!gconfig.renderer){
285
+ gconfig.renderer = "line";
286
+ }
287
+
288
+ graph.configure(gconfig);
289
+ graph.render();
290
+ }
291
+
292
+
293
+ function announce(evt){
294
+ if(evt.widget_key == opts.widget_key){
295
+ if((evt.type == "widget_response") && (evt.cmd == "values_at")){
296
+ running_request = false;
297
+ $(opts.elem).css('opacity', 1);
298
+ updateSeriesData(evt.gauges);
299
+ }
300
+ }
301
+ }
302
+
303
+ function requestValuesAsync(){
304
+ FnordMetric.publish({
305
+ "type": "widget_request",
306
+ "klass": "generic",
307
+ "gauges": opts.gauges,
308
+ "cmd": "values_at",
309
+ "tick": opts.tick,
310
+ "since": opts.start_timestamp,
311
+ "until": opts.end_timestamp,
312
+ "widget_key": opts.widget_key
313
+ });
314
+ }
315
+
316
+ function updateSeriesData(dgauges){
317
+ gconfig.series = [];
318
+
319
+ for(var ind = 0; ind < dgauges.length; ind++){
320
+
321
+ gconfig.series.push({
322
+ name: dgauges[ind].title,
323
+ color: opts.series[ind].color,
324
+ data: []
325
+ });
326
+
327
+ for(_time in dgauges[ind].vals){
328
+ gconfig.series[ind].data.push(
329
+ { x: parseInt(_time), y: parseInt(dgauges[ind].vals[_time] || 0) }
330
+ );
331
+ }
332
+
333
+ }
334
+
335
+ renderChart();
336
+ }
337
+
338
+ function updateChart(first_time, silent){
339
+ if(!silent){ $(opts.elem).css('opacity', 0.7); }
340
+ updateRange();
341
+ redrawDatepicker();
342
+ requestValuesAsync();
343
+ }
344
+
345
+ function redrawDatepicker(){
346
+ $('.datepicker', opts.elem).html(
347
+ FnordMetric.util.dateFormat(opts.start_timestamp) +
348
+ '&nbsp;&dash;&nbsp;' +
349
+ FnordMetric.util.dateFormat(opts.end_timestamp)
350
+ );
351
+ }
352
+
353
+ function updateRange(force){
354
+ var _now = parseInt(new Date().getTime() / 1000);
355
+
356
+ if (opts.no_update_range)
357
+ return;
358
+
359
+ if (opts.ext) {
360
+ opts.end_timestamp = _now;
361
+ opts.start_timestamp = _now - opts.trange;
362
+ return;
363
+ }
364
+
365
+ if(!opts.tick){
366
+ opts.tick = opts.ticks[0];
367
+ }
368
+
369
+ if((opts.autoupdate) &&
370
+ ((_now - opts.end_timestamp) < ((opts.tick*(opts.autoupdate+1)) + 5))){
371
+ force = true;
372
+ }
373
+
374
+ if(!opts.start_timestamp || !opts.end_timestamp || !!force){
375
+ opts.end_timestamp = _now + opts.tick;
376
+ opts.start_timestamp = opts.end_timestamp - (opts.tick * xticks);
377
+ }
378
+ }
379
+
380
+ function moveRange(direction){
381
+ v = opts.tick*direction*8;
382
+
383
+ if(((opts.end_timestamp + v)*1000) < new Date().getTime()){
384
+ opts.start_timestamp += v;
385
+ opts.end_timestamp += v;
386
+ }
387
+
388
+ redrawDatepicker();
389
+ requestValuesAsync();
390
+ }
391
+
392
+ function set_timerange(s, e){
393
+ opts.start_timestamp = s;
394
+ opts.end_timestamp = e;
395
+ redrawDatepicker();
396
+ requestValuesAsync();
397
+ }
398
+
399
+ return {
400
+ render: render,
401
+ announce: announce,
402
+ set_timerange: set_timerange
403
+ }
404
+
405
+ };