scout_realtime 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.5.4
2
+
3
+ * Misc UI updates
4
+
1
5
  ## 0.5.3
2
6
 
3
7
  * Update executable output - instructions for accessing from your browser.
@@ -1,7 +1,7 @@
1
1
  class Scout::Realtime::Processes < Scout::Realtime::Metric
2
2
  include Scout::Realtime::MultiAggregator
3
3
 
4
- FIELDS = { :cpu => {'label'=>'CPU usage', 'units'=>'', 'precision'=>2},
4
+ FIELDS = { :cpu => {'label'=>'CPU usage', 'units'=>'%', 'precision'=>2},
5
5
  :memory => {'units'=>'MB', 'precision'=>1},
6
6
  :count => {'units'=>'', 'precision'=>0}
7
7
  }
@@ -1,5 +1,5 @@
1
1
  module Scout
2
2
  module Realtime
3
- VERSION = "0.5.3"
3
+ VERSION = "0.5.4"
4
4
  end
5
5
  end
Binary file
@@ -1,31 +1,30 @@
1
1
  var refreshInterval = 1000;
2
2
  var processIteration = 0;
3
+ var firstShift = true;
4
+ var overviewChartMargin = 30; // pulled from lineChart overview margin
3
5
  function d(s){console.debug(s)}
4
6
 
5
-
6
- $(function(){ setChartSizes()})
7
-
8
7
  window.refresher = null;
9
8
  function toggleData() {
10
9
  if (window.refresher) {
11
10
  clearInterval(window.refresher);
12
11
  window.refresher = null;
13
- $("#toggle .on-button").addClass("disabled");
14
- $("#toggle .off-button").removeClass("disabled");
12
+ $("#toggle #on.button").addClass("disabled");
13
+ $("#toggle #off.button").removeClass("disabled");
15
14
  } else {
16
15
  window.refresher = setInterval(refresh, refreshInterval);
17
- $("#toggle .on-button").removeClass("disabled");
18
- $("#toggle .off-button").addClass("disabled");
16
+ $("#toggle #on.button").removeClass("disabled");
17
+ $("#toggle #off.button").addClass("disabled");
19
18
  }
20
19
  }
21
20
 
22
21
  var graphs = {};
23
22
 
24
23
  var Processes={
25
- index:{},
26
- element:null,
27
- tableBody:null,
28
- tableRowTemplate:null,
24
+ index:{}, // processName: {count:chart, cpu:chart, memory:chart, row:jQueryObject}
25
+ element:null, // passed on init
26
+ tableBody:null, // jQuery object, saved here for fast access
27
+ tableRowTemplate:null, // a handlebars template - is compiled on init
29
28
  sortBy:"memory",
30
29
 
31
30
  init:function(opts){
@@ -33,7 +32,16 @@ var Processes={
33
32
  Processes.element=opts.element
34
33
  Processes.tableBody=$(Processes.element).find("tbody");
35
34
  Processes.tableRowTemplate = Handlebars.compile($("#process-template").html());
36
- console.debug("Finished Processes.init")
35
+
36
+ // process sorting
37
+ $('#processes th[data-sort-by="memory"] .sort-down').show();
38
+ $(Processes.element).find("th").hover().click(function(e){
39
+ $this=$(this);
40
+ $(Processes.element).find('.sort-down, .sort-up').hide();
41
+ $this.find('.sort-down').show();
42
+ Processes.setSort($this.data('sort-by'));
43
+ e.stopPropagation();
44
+ })
37
45
  },
38
46
 
39
47
  update:function(raw_processes){
@@ -59,8 +67,6 @@ var Processes={
59
67
  }
60
68
  }
61
69
  }
62
-
63
- this.initialUpdateCompleted = true;
64
70
 
65
71
  // 3. add data and update charts
66
72
  for(cmd in Processes.index) {
@@ -76,12 +82,12 @@ var Processes={
76
82
  });
77
83
 
78
84
  if(data) {
79
- Processes.index[cmd]['row'].data({'memory':data['memory'],'cpu':data['cpu']}); // raw numbers for sorting
85
+ Processes.index[cmd]['row'].data({'memory':data['memory'],'cpu':data['cpu'],'count':data['count']}); // raw numbers for sorting
80
86
  }
81
87
  }
82
88
 
83
89
  if(processIteration % 10 == 0) {
84
- // TODO 3. reorder table rows as needed
90
+ // 4. reorder table rows as needed
85
91
  rows = Processes.tableBody.children('tr').get();
86
92
  rows.sort(function(a, b) {
87
93
  var compA = $(a).data(Processes.sortBy);
@@ -119,8 +125,7 @@ var Processes={
119
125
  $.each(bufferedData, function(data_index, value) {
120
126
  data.push({time: startTime + data_index * 1000, value: [value]})
121
127
  });
122
-
123
- chart = new lineChart({ element: $(this).get(0), data: data, metadata: meta.processes[metric] });
128
+ chart = new lineChart({ element: $(this).get(0), data: data, metadata: meta.processes[metric], type: 'process' });
124
129
  chart.draw();
125
130
  Processes.index[cmd][metric] = chart;
126
131
  });
@@ -129,6 +134,13 @@ var Processes={
129
134
  remove:function(cmd){
130
135
  Processes.index[cmd]['row'].remove();
131
136
  delete Processes.index[cmd];
137
+ },
138
+ setSort:function(sortBy){
139
+ if (sortBy != Processes.sortBy) {
140
+ Processes.sortBy = sortBy;
141
+ processIteration = 0;
142
+ Processes.update(metrics['processes']);
143
+ }
132
144
  }
133
145
  }
134
146
 
@@ -137,8 +149,13 @@ var Processes={
137
149
  $(document).ready(init);
138
150
 
139
151
  function init() {
140
- $("#sort_cpu, #sort_memory").change(function(){Processes.sortBy=this.value; d("sort by "+this.value)})
141
- Processes.init({element:document.getElementById('processes')})
152
+ Processes.init({element:document.getElementById('processes')});
153
+ setOverviewChartWidths();
154
+
155
+ $(window).on("debouncedresize", function( event ) {
156
+ setOverviewChartWidths();
157
+ });
158
+
142
159
  $(".overview_chart").each(function (i) {
143
160
  var $chart = $(this);
144
161
  var collector = $chart.data("collector");
@@ -172,8 +189,7 @@ function init() {
172
189
 
173
190
  data.push({time: startTime + data_index * 1000, value: [valueSum], valueBreakdown: valueBreakdown})
174
191
  });
175
-
176
- chart = new lineChart({ element: $chart.get(0), data: data, yScaleMax: yScaleMax, metadata: meta[collector][chartMetrics[0]] });
192
+ chart = new lineChart({ element: $chart.get(0), data: data, yScaleMax: yScaleMax, metadata: (chartMetrics.length > 1 ? meta[collector] : meta[collector][chartMetrics[0]]), type: 'overview' });
177
193
  chart.draw();
178
194
  graphs[id] = chart;
179
195
  } else {
@@ -181,7 +197,7 @@ function init() {
181
197
  }
182
198
  });
183
199
 
184
- $('#toggle').on('click', function() {
200
+ $('#toggle .button').on('click', function() {
185
201
  toggleData();
186
202
  });
187
203
 
@@ -189,9 +205,15 @@ function init() {
189
205
  toggleData(); // uncomment to get continuous data
190
206
  // setTimeout(updateGraphData, 1000) // uncomment to get ONE update in a second (for development)
191
207
  // normally, lines 1 and 2 above will be uncommented
208
+
209
+ // in chrome, the initial chart size is larger than what the parent container is. calling it here resets things.
210
+ // still need to call it before drawing charts as FF won't draw it full width.
211
+ setOverviewChartWidths()
212
+
192
213
  }
193
214
 
194
215
  function updateGraphData() {
216
+
195
217
  Processes.update(metrics['processes']);
196
218
 
197
219
  $(".overview_chart").each(function (i) {
@@ -218,15 +240,20 @@ function updateGraphData() {
218
240
  });
219
241
 
220
242
  graph.data.push({time: new Date().getTime(), value: [valueSum], valueBreakdown: valueBreakdown});
221
- graph.data.shift();
243
+ if(firstShift) {
244
+ // only don't shift off the data on the first iteration. leave it a tail so the graph does not clip along choppily
245
+ firstShift = false;
246
+ } else {
247
+ graph.data.shift();
248
+ }
222
249
  }
223
250
  });
224
251
 
225
252
  $('#report-time').html(formatTime(new Date));
226
253
  $('#memory-used').html(parseInt(metrics.memory.used));
227
- $('#memory-available').html(parseInt(metrics.memory.size));
254
+ $('#memory-size').html(parseInt(metrics.memory.size));
228
255
  $('#disk-used').html(parseInt(metrics.disk[Object.keys(metrics.disk)[0]].used));
229
- $('#disk-available').html(parseInt(metrics.disk[Object.keys(metrics.disk)[0]].avail));
256
+ $('#disk-size').html(parseInt(metrics.disk[Object.keys(metrics.disk)[0]].size));
230
257
  }
231
258
 
232
259
  function formatTime(timestamp) {
@@ -256,10 +283,25 @@ function refresh() {
256
283
 
257
284
  /*
258
285
  Called on initial load. Just sets the svg element to the size of its parent. This is needed on firefox.
286
+
287
+ Why don't we just use % widths for containers?
288
+
289
+ The chart SVGs have a 30px left margin to handle the y-axis. We need to shift the charts over 30px to the left
290
+ to handle this. We also need to add 30px to the width of the chart so it ends up using the entire container width
291
+ after the 30px left shift.
292
+
259
293
  */
260
- function setChartSizes(){
294
+ function setOverviewChartWidths(){
295
+ $overview_charts_td = $('#overview_charts');
296
+ totalWidth = $overview_charts_td.width(); // width of the td container for the 4 overview charts
297
+ padding = parseInt($overview_charts_td.css('padding-left'));
298
+ containerWidth = (totalWidth-2*padding-5)/2; // why -5? chrome overlaps at -4, ff at -5. don't know why.
299
+ $('.overview_chart_container').each(function(i,e) {
300
+ $(e).width(containerWidth).css('margin-right',padding)
301
+ });
302
+
261
303
  $('.overview_chart').each(function(i,e){
262
304
  $svg=$(e);
263
- $svg.width($svg.parent().width());
305
+ $svg.width(containerWidth+overviewChartMargin);
264
306
  });
265
307
  }
@@ -3,15 +3,24 @@ function lineChart(params) {
3
3
  this.yScaleMax = params.yScaleMax;
4
4
  this.element = params.element;
5
5
  this.metadata = params.metadata;
6
- var line, path, x, y, barWidth, height, selection, chartInfo, latestValue, chartId;
6
+ // charts can plot multiple metrics. if so, we assume units are the same. fetch that data from the first metric.
7
+ this.shared_metadata = ('units' in this.metadata ? this.metadata : this.metadata[Object.keys(this.metadata)[0]]);
8
+
9
+ var line, path, x, y, barWidth, height, selection, chartInfo, latestValue, chartId, xAxis, yAxis;
10
+ var type = params.type;
11
+ if(type == 'overview') {
12
+ var margin = { top: 20, right: 0, bottom: 20, left: 30 };
13
+ } else {
14
+ var margin = { top: 0, right: 0, bottom: 0, left: 0 };
15
+ }
7
16
 
8
17
  this.draw = function() {
9
18
  var _this = this;
10
19
  selection = d3.select(this.element);
11
20
  chartId = $(selection[0]).data('collector') + $(selection[0]).data('instance-name');
12
21
  // get the width and height form the selection
13
- var width = selection.node().clientWidth || parseInt($(selection.node()).css('width'));
14
- height = selection.node().clientHeight || parseInt($(selection.node()).css('height'));
22
+ var width = (selection.node().clientWidth || parseInt($(selection.node()).css('width'))) - margin.right; // don't take left margin into account for smoothness' sake
23
+ height = (selection.node().clientHeight || parseInt($(selection.node()).css('height'))) - margin.bottom - margin.top;
15
24
  y = d3.scale.linear().range([height, 0]);
16
25
  x = d3.time.scale().range([0, width]);
17
26
  now = new Date();
@@ -32,7 +41,8 @@ function lineChart(params) {
32
41
  .attr("class", "clip-path")
33
42
  .append("rect")
34
43
  .attr("width", width - (barWidth * 2)) // new data points are drawn outside of clip area
35
- .attr("height", height);
44
+ .attr("height", height)
45
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
36
46
 
37
47
  line = d3.svg.line()
38
48
  .interpolate("basis")
@@ -40,6 +50,18 @@ function lineChart(params) {
40
50
  .y(function(d, i) { return y(d.value[0]); });
41
51
 
42
52
 
53
+ if(type == 'overview') {
54
+ xAxis = selection.append("g")
55
+ .attr("class", 'x axis')
56
+ .attr("transform", "translate(" + margin.left + "," + (height + margin.top) + ")")
57
+ .call(x.axis = d3.svg.axis().scale(x).ticks(d3.time.seconds, 15).tickFormat(d3.time.format("%I:%M:%S")).orient("bottom"));
58
+
59
+ yAxis = selection.append("g")
60
+ .attr("class", 'y axis')
61
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
62
+ .call(y.axis = d3.svg.axis().scale(y).ticks(1).orient("right"));
63
+ }
64
+
43
65
  chartInfo = $(selection[0]).parent().siblings('.chart_info');
44
66
  latestValue = $(selection[0]).parent().siblings('.latest_value');
45
67
  latestValue.html(this.format(this.data[this.data.length - 1].value[0]));
@@ -52,13 +74,22 @@ function lineChart(params) {
52
74
  .attr("class", "chart_section")
53
75
  .attr("width", barWidth)
54
76
  .attr("height", height)
55
- .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; })
77
+ .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; })
78
+ // for overview charts, the latest value stays visible and the mouseover values appear beneath the chart.
79
+ // because of space constraints, the latest value is hidden in process charts and the mouseover value takes its place.
56
80
  .on("mouseover", function(d) {
57
- //latestValue.html(_this.format(d.value[0]));
58
- chartInfo.html(_this.tooltip(d));
81
+ chartInfo.html(_this.tooltip(d))
82
+ if (chartInfo.css('display') == 'none') {
83
+ latestValue.hide();
84
+ chartInfo.show();
85
+ }
59
86
  })
60
87
  .on("mouseout", function(d) {
61
- chartInfo.html("");
88
+ chartInfo.html("")
89
+ if (latestValue.css('display') == 'none') {
90
+ latestValue.show();
91
+ chartInfo.hide();
92
+ }
62
93
  });
63
94
 
64
95
  path = selection.append("g")
@@ -87,19 +118,31 @@ function lineChart(params) {
87
118
 
88
119
  latestValue.html(this.format(this.data[this.data.length - 1].value[0]));
89
120
 
121
+ var translateTarget = x(x.domain()[0] - refreshInterval) + margin.left;
122
+
90
123
  path
91
124
  // update the line - this isn't part of the transition. new points will be off to the right.
92
125
  .attr("d", line)
93
- .attr("transform", null)
126
+ .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
94
127
  // slide the path to the left 1 second
95
128
  .transition()
96
129
  .duration(refreshInterval - 50) // duration is the same as the update interval...if it equals the update interval, it gets canceled?
97
130
  .ease("linear")
98
- .attr("transform", "translate(" + x(x.domain()[0] - refreshInterval) + ")")
131
+ .attr("transform", "translate(" + translateTarget + "," + margin.top + ")")
99
132
  .each("end", function() {
100
133
  _this.refresh();
101
134
  });
102
135
 
136
+ if(type == 'overview') {
137
+ xAxis.transition()
138
+ .duration(refreshInterval - 50)
139
+ .ease("linear")
140
+ .call(x.axis);
141
+
142
+ yAxis.transition()
143
+ .call(y.axis);
144
+ }
145
+
103
146
  chartSection = selection.selectAll("rect")
104
147
  .data(this.data);
105
148
 
@@ -108,7 +151,7 @@ function lineChart(params) {
108
151
  .attr("class", "chart_section")
109
152
  .attr("width", barWidth)
110
153
  .attr("height", height)
111
- .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; })
154
+ .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; })
112
155
  .on("mouseover", function(d) {
113
156
  chartInfo.html(_this.tooltip(d));
114
157
  })
@@ -117,7 +160,7 @@ function lineChart(params) {
117
160
  })
118
161
  .transition()
119
162
  .duration(0)
120
- .attr("transform", function(d, i) { return "translate(" + (x(d.time) - barWidth) + ",0)"; })
163
+ .attr("transform", function(d, i) { return "translate(" + ((x(d.time) - barWidth) + margin.left) + "," + margin.top + ")"; })
121
164
  } else {
122
165
  setTimeout(function() { _this.refresh() }, refreshInterval);
123
166
  }
@@ -148,18 +191,23 @@ function lineChart(params) {
148
191
  }
149
192
 
150
193
  this.format = function(value) {
151
- var formattedUnits = ( this.metadata.units == '%' ? '%' : ' ' + this.metadata.units )
152
- return(value.toFixed(this.metadata.precision) + formattedUnits);
194
+ var formattedUnits = ( this.shared_metadata.units == '%' ? '%' : '&nbsp;' + this.shared_metadata.units )
195
+ return(value.toFixed(this.shared_metadata.precision) + formattedUnits);
153
196
  }
154
197
 
155
198
  this.tooltip = function(data) {
156
199
  var tooltipStr = '';
157
- if(data.valueBreakdown) {
200
+ // if multiple values (ex: io wait, system, user), display the metric label + value. otherwise,
201
+ // just display the value.
202
+ if(data.valueBreakdown && Object.keys(data.valueBreakdown).length > 1) {
158
203
  for(var key in data.valueBreakdown) {
159
- tooltipStr += key + ': ' + this.format(data.valueBreakdown[key]) + '; ';
204
+ var label = ('units' in this.metadata ? this.metadata.label : this.metadata[key].label);
205
+ if (!label) { label = key };
206
+ // metrics aren't guarenteed to have a label. use it if they do.
207
+ tooltipStr += '<span>'+label + '<span>' + this.format(data.valueBreakdown[key]) + '</span></span>';
160
208
  }
161
209
  } else {
162
- tooltipStr = this.format(data.value[0]);
210
+ tooltipStr = '<span><span>'+this.format(data.value[0])+'</span></span>';
163
211
  }
164
212
  return tooltipStr;
165
213
  }
@@ -0,0 +1,47 @@
1
+ /*
2
+ * debouncedresize: special jQuery event that happens once after a window resize
3
+ *
4
+ * latest version and complete README available on Github:
5
+ * https://github.com/louisremi/jquery-smartresize
6
+ *
7
+ * Copyright 2012 @louis_remi
8
+ * Licensed under the MIT license.
9
+ *
10
+ * This saved you an hour of work?
11
+ * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON
12
+ */
13
+ (function($) {
14
+
15
+ var $event = $.event,
16
+ $special,
17
+ resizeTimeout;
18
+
19
+ $special = $event.special.debouncedresize = {
20
+ setup: function() {
21
+ $( this ).on( "resize", $special.handler );
22
+ },
23
+ teardown: function() {
24
+ $( this ).off( "resize", $special.handler );
25
+ },
26
+ handler: function( event, execAsap ) {
27
+ // Save the context
28
+ var context = this,
29
+ args = arguments,
30
+ dispatch = function() {
31
+ // set correct event type
32
+ event.type = "debouncedresize";
33
+ $event.dispatch.apply( context, args );
34
+ };
35
+
36
+ if ( resizeTimeout ) {
37
+ clearTimeout( resizeTimeout );
38
+ }
39
+
40
+ execAsap ?
41
+ dispatch() :
42
+ resizeTimeout = setTimeout( dispatch, $special.threshold );
43
+ },
44
+ threshold: 150
45
+ };
46
+
47
+ })(jQuery);