scout_realtime 0.5.3 → 0.5.4

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.
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);