pulse-meter 0.0.1

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.
Files changed (96) hide show
  1. data/.gitignore +19 -0
  2. data/.rbenv-version +1 -0
  3. data/.rspec +1 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +4 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +22 -0
  8. data/Procfile +3 -0
  9. data/README.md +440 -0
  10. data/Rakefile +53 -0
  11. data/bin/pulse +6 -0
  12. data/examples/basic.ru +109 -0
  13. data/examples/basic_sensor_data.rb +38 -0
  14. data/examples/full/Procfile +2 -0
  15. data/examples/full/client.rb +82 -0
  16. data/examples/full/server.ru +114 -0
  17. data/examples/minimal/Procfile +2 -0
  18. data/examples/minimal/client.rb +16 -0
  19. data/examples/minimal/server.ru +20 -0
  20. data/examples/readme_client_example.rb +52 -0
  21. data/lib/cmd.rb +150 -0
  22. data/lib/pulse-meter.rb +17 -0
  23. data/lib/pulse-meter/mixins/dumper.rb +72 -0
  24. data/lib/pulse-meter/mixins/utils.rb +91 -0
  25. data/lib/pulse-meter/sensor.rb +44 -0
  26. data/lib/pulse-meter/sensor/base.rb +75 -0
  27. data/lib/pulse-meter/sensor/counter.rb +36 -0
  28. data/lib/pulse-meter/sensor/hashed_counter.rb +31 -0
  29. data/lib/pulse-meter/sensor/indicator.rb +33 -0
  30. data/lib/pulse-meter/sensor/timeline.rb +180 -0
  31. data/lib/pulse-meter/sensor/timelined/average.rb +26 -0
  32. data/lib/pulse-meter/sensor/timelined/counter.rb +16 -0
  33. data/lib/pulse-meter/sensor/timelined/hashed_counter.rb +22 -0
  34. data/lib/pulse-meter/sensor/timelined/max.rb +25 -0
  35. data/lib/pulse-meter/sensor/timelined/median.rb +14 -0
  36. data/lib/pulse-meter/sensor/timelined/min.rb +25 -0
  37. data/lib/pulse-meter/sensor/timelined/percentile.rb +31 -0
  38. data/lib/pulse-meter/version.rb +3 -0
  39. data/lib/pulse-meter/visualize/app.rb +43 -0
  40. data/lib/pulse-meter/visualize/dsl.rb +0 -0
  41. data/lib/pulse-meter/visualize/dsl/errors.rb +46 -0
  42. data/lib/pulse-meter/visualize/dsl/layout.rb +55 -0
  43. data/lib/pulse-meter/visualize/dsl/page.rb +50 -0
  44. data/lib/pulse-meter/visualize/dsl/sensor.rb +21 -0
  45. data/lib/pulse-meter/visualize/dsl/widget.rb +84 -0
  46. data/lib/pulse-meter/visualize/layout.rb +54 -0
  47. data/lib/pulse-meter/visualize/page.rb +30 -0
  48. data/lib/pulse-meter/visualize/public/css/application.css +19 -0
  49. data/lib/pulse-meter/visualize/public/css/bootstrap.css +4883 -0
  50. data/lib/pulse-meter/visualize/public/css/bootstrap.min.css +729 -0
  51. data/lib/pulse-meter/visualize/public/favicon.ico +0 -0
  52. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings-white.png +0 -0
  53. data/lib/pulse-meter/visualize/public/img/glyphicons-halflings.png +0 -0
  54. data/lib/pulse-meter/visualize/public/js/application.coffee +262 -0
  55. data/lib/pulse-meter/visualize/public/js/application.js +279 -0
  56. data/lib/pulse-meter/visualize/public/js/backbone-min.js +38 -0
  57. data/lib/pulse-meter/visualize/public/js/bootstrap.js +1835 -0
  58. data/lib/pulse-meter/visualize/public/js/highcharts.js +203 -0
  59. data/lib/pulse-meter/visualize/public/js/jquery-1.7.2.min.js +4 -0
  60. data/lib/pulse-meter/visualize/public/js/json2.js +487 -0
  61. data/lib/pulse-meter/visualize/public/js/underscore-min.js +32 -0
  62. data/lib/pulse-meter/visualize/sensor.rb +60 -0
  63. data/lib/pulse-meter/visualize/views/main.haml +40 -0
  64. data/lib/pulse-meter/visualize/widget.rb +68 -0
  65. data/lib/pulse-meter/visualizer.rb +30 -0
  66. data/lib/test_helpers/matchers.rb +36 -0
  67. data/pulse-meter.gemspec +39 -0
  68. data/spec/pulse_meter/mixins/dumper_spec.rb +158 -0
  69. data/spec/pulse_meter/mixins/utils_spec.rb +134 -0
  70. data/spec/pulse_meter/sensor/base_spec.rb +97 -0
  71. data/spec/pulse_meter/sensor/counter_spec.rb +54 -0
  72. data/spec/pulse_meter/sensor/hashed_counter_spec.rb +39 -0
  73. data/spec/pulse_meter/sensor/indicator_spec.rb +43 -0
  74. data/spec/pulse_meter/sensor/timeline_spec.rb +58 -0
  75. data/spec/pulse_meter/sensor/timelined/average_spec.rb +6 -0
  76. data/spec/pulse_meter/sensor/timelined/counter_spec.rb +6 -0
  77. data/spec/pulse_meter/sensor/timelined/hashed_counter_spec.rb +8 -0
  78. data/spec/pulse_meter/sensor/timelined/max_spec.rb +7 -0
  79. data/spec/pulse_meter/sensor/timelined/median_spec.rb +7 -0
  80. data/spec/pulse_meter/sensor/timelined/min_spec.rb +7 -0
  81. data/spec/pulse_meter/sensor/timelined/percentile_spec.rb +17 -0
  82. data/spec/pulse_meter/visualize/app_spec.rb +27 -0
  83. data/spec/pulse_meter/visualize/dsl/layout_spec.rb +64 -0
  84. data/spec/pulse_meter/visualize/dsl/page_spec.rb +75 -0
  85. data/spec/pulse_meter/visualize/dsl/sensor_spec.rb +30 -0
  86. data/spec/pulse_meter/visualize/dsl/widget_spec.rb +127 -0
  87. data/spec/pulse_meter/visualize/layout_spec.rb +55 -0
  88. data/spec/pulse_meter/visualize/page_spec.rb +150 -0
  89. data/spec/pulse_meter/visualize/sensor_spec.rb +120 -0
  90. data/spec/pulse_meter/visualize/widget_spec.rb +113 -0
  91. data/spec/pulse_meter/visualizer_spec.rb +42 -0
  92. data/spec/pulse_meter_spec.rb +16 -0
  93. data/spec/shared_examples/timeline_sensor.rb +279 -0
  94. data/spec/shared_examples/timelined_subclass.rb +23 -0
  95. data/spec/spec_helper.rb +29 -0
  96. metadata +435 -0
@@ -0,0 +1,262 @@
1
+ $ ->
2
+ globalOptions = gon.options
3
+
4
+ Highcharts.setOptions {
5
+ global: {
6
+ useUTC: globalOptions.useUtc
7
+ }
8
+ }
9
+
10
+ PageInfo = Backbone.Model.extend {
11
+ }
12
+
13
+ PageInfoList = Backbone.Collection.extend {
14
+ model: PageInfo
15
+ selected: ->
16
+ @find (m) ->
17
+ m.get 'selected'
18
+
19
+
20
+ selectFirst: ->
21
+ @at(0).set('selected', true) if @length > 0
22
+
23
+ selectPage: (id) ->
24
+ @each (m) ->
25
+ m.set 'selected', m.id == id
26
+ }
27
+
28
+ pageInfos = new PageInfoList
29
+
30
+ PageTitleView = Backbone.View.extend {
31
+ tagName: 'li'
32
+
33
+ template: _.template('<a href="#/pages/<%= id %>"><%= title %></a>')
34
+
35
+ initialize: ->
36
+ @model.bind 'change', @render, this
37
+ @model.bind 'destroy', @remove, this
38
+
39
+ render: ->
40
+ @$el.html @template(@model.toJSON())
41
+ if @model.get('selected')
42
+ @$el.addClass('active')
43
+ else
44
+ @$el.removeClass('active')
45
+ }
46
+
47
+ PageTitlesView = Backbone.View.extend {
48
+ initialize: ->
49
+ pageInfos.bind 'reset', @render, this
50
+
51
+ addOne: (pageInfo) ->
52
+ view = new PageTitleView {
53
+ model: pageInfo
54
+ }
55
+ view.render()
56
+ $('#page-titles').append(view.el)
57
+
58
+ render: ->
59
+ $('#page-titles').empty()
60
+ pageInfos.each(@addOne)
61
+ }
62
+
63
+ pageTitlesApp = new PageTitlesView
64
+
65
+ pageInfos.reset gon.pageInfos
66
+
67
+ Widget = Backbone.Model.extend {
68
+ initialize: ->
69
+ @needRefresh = true
70
+ @setNextFetch()
71
+
72
+ time: -> (new Date()).getTime()
73
+
74
+ setNextFetch: ->
75
+ @nextFetch = @time() + @get('interval') * 1000
76
+
77
+ setRefresh: (needRefresh) ->
78
+ @needRefresh = needRefresh
79
+
80
+ needFetch: ->
81
+ interval = @get('interval')
82
+ @time() > @nextFetch && @needRefresh && interval? && interval > 0
83
+
84
+ refetch: ->
85
+ if @needFetch()
86
+ @fetch()
87
+ @setNextFetch()
88
+
89
+ cutoffValue: (v, min, max) ->
90
+ if min isnt null && v.y < min
91
+ v.y = min
92
+ v.color = globalOptions.outlierColor
93
+ if max isnt null && v.y > max
94
+ v.y = max
95
+ v.color = globalOptions.outlierColor
96
+
97
+ cutoff: (min, max) ->
98
+ _.each(@get('series'), (s) ->
99
+ _.each( s.data, (v) ->
100
+ @cutoffValue(v, min, max)
101
+ , this)
102
+ , this)
103
+ }
104
+
105
+ WidgetList = Backbone.Collection.extend {
106
+ model: Widget
107
+ url: ->
108
+ ROOT + 'pages/' + pageInfos.selected().id + '/widgets'
109
+ }
110
+
111
+ WidgetChartView = Backbone.View.extend {
112
+ tagName: 'div'
113
+
114
+ initialize: ->
115
+ @model.bind 'destroy', @remove, this
116
+
117
+ updateData: (min, max) ->
118
+ @model.cutoff(min, max)
119
+ chartSeries = @chart.series
120
+ newSeries = @model.get('series')
121
+ for i in [0 .. chartSeries.length - 1]
122
+ if newSeries[i]?
123
+ chartSeries[i].setData(newSeries[i].data, false)
124
+ @chart.redraw()
125
+
126
+ render: ->
127
+ options = {
128
+ chart: {
129
+ renderTo: @el
130
+ plotBorderWidth: 1
131
+ spacingLeft: 0
132
+ spacingRight: 0
133
+ type: @model.get('type')
134
+ animation: false
135
+ zoomType: 'x'
136
+ }
137
+ credits: {
138
+ enabled: false
139
+ }
140
+ title: {
141
+ text: @model.get('title')
142
+ }
143
+ xAxis: {
144
+ type: 'datetime'
145
+ }
146
+ yAxis: {
147
+ title: {
148
+ text: @model.get('valuesTitle')
149
+ }
150
+ }
151
+ tooltip: {
152
+ xDateFormat: '%Y-%m-%d %H:%M:%S'
153
+ valueDecimals: 6
154
+ }
155
+ series: @model.get('series')
156
+ plotOptions: {
157
+ series: {
158
+ animation: false
159
+ lineWidth: 1
160
+ shadow: false
161
+ marker: {
162
+ radius: 0
163
+ }
164
+ }
165
+ }
166
+ }
167
+ $.extend(true, options, globalOptions.highchartOptions, pageInfos.selected().get('highchartOptions'))
168
+ @chart = new Highcharts.Chart(options)
169
+
170
+ }
171
+
172
+ WidgetView = Backbone.View.extend {
173
+ tagName: 'div'
174
+
175
+ template: _.template($('#widget-template').html())
176
+
177
+ initialize: ->
178
+ @model.bind('destroy', @remove, this)
179
+ @model.bind('change', @updateChart, this)
180
+
181
+ events: {
182
+ "click #refresh": 'refresh'
183
+ "click #need-refresh": 'setRefresh'
184
+ }
185
+
186
+ refresh: ->
187
+ @model.fetch()
188
+
189
+ setRefresh: ->
190
+ needRefresh = @$el.find('#need-refresh').is(":checked")
191
+ @model.setRefresh(needRefresh)
192
+ true
193
+
194
+ renderChart: ->
195
+ @chartView.render()
196
+
197
+ updateChart: ->
198
+ @chartView.updateData(@cutoffMin(), @cutoffMax())
199
+
200
+ render: ->
201
+ @$el.html @template(@model.toJSON())
202
+ @chartView = new WidgetChartView {
203
+ model: @model
204
+ }
205
+ @$el.find("#plotarea").append(@chartView.el)
206
+ @$el.addClass "span#{@model.get('width')}"
207
+
208
+ cutoffMin: ->
209
+ val = parseFloat(@controlValue('#cutoff-min'))
210
+ if _.isNaN(val) then null else val
211
+
212
+ cutoffMax: ->
213
+ val = parseFloat(@controlValue('#cutoff-max'))
214
+ if _.isNaN(val) then null else val
215
+
216
+ controlValue: (id) ->
217
+ val = @$el.find(id).first().val()
218
+
219
+
220
+ }
221
+
222
+ widgetList = new WidgetList
223
+ setInterval( ->
224
+ widgetList.each (w) ->
225
+ w.refetch()
226
+ , 200)
227
+
228
+ WidgetListView = Backbone.View.extend {
229
+ initialize: ->
230
+ widgetList.bind 'reset', @render, this
231
+
232
+ render: ->
233
+ container = $('#widgets')
234
+ container.empty()
235
+ widgetList.each (w) ->
236
+ view = new WidgetView {
237
+ model: w
238
+ }
239
+ view.render()
240
+ container.append(view.el)
241
+ view.renderChart()
242
+ }
243
+
244
+ widgetListApp = new WidgetListView
245
+
246
+ AppRouter = Backbone.Router.extend {
247
+ routes: {
248
+ 'pages/:id': 'getPage'
249
+ '*actions': 'defaultRoute'
250
+ }
251
+ getPage: (ids) ->
252
+ id = parseInt(ids)
253
+ pageInfos.selectPage(id)
254
+ widgetList.fetch()
255
+ defaultRoute: (actions) ->
256
+ @navigate('//pages/1') if pageInfos.length > 0
257
+ }
258
+
259
+ appRouter = new AppRouter
260
+
261
+ Backbone.history.start()
262
+
@@ -0,0 +1,279 @@
1
+ // Generated by CoffeeScript 1.2.1-pre
2
+ (function() {
3
+
4
+ $(function() {
5
+ var AppRouter, PageInfo, PageInfoList, PageTitleView, PageTitlesView, Widget, WidgetChartView, WidgetList, WidgetListView, WidgetView, appRouter, globalOptions, pageInfos, pageTitlesApp, widgetList, widgetListApp;
6
+ globalOptions = gon.options;
7
+ Highcharts.setOptions({
8
+ global: {
9
+ useUTC: globalOptions.useUtc
10
+ }
11
+ });
12
+ PageInfo = Backbone.Model.extend({});
13
+ PageInfoList = Backbone.Collection.extend({
14
+ model: PageInfo,
15
+ selected: function() {
16
+ return this.find(function(m) {
17
+ return m.get('selected');
18
+ });
19
+ },
20
+ selectFirst: function() {
21
+ if (this.length > 0) return this.at(0).set('selected', true);
22
+ },
23
+ selectPage: function(id) {
24
+ return this.each(function(m) {
25
+ return m.set('selected', m.id === id);
26
+ });
27
+ }
28
+ });
29
+ pageInfos = new PageInfoList;
30
+ PageTitleView = Backbone.View.extend({
31
+ tagName: 'li',
32
+ template: _.template('<a href="#/pages/<%= id %>"><%= title %></a>'),
33
+ initialize: function() {
34
+ this.model.bind('change', this.render, this);
35
+ return this.model.bind('destroy', this.remove, this);
36
+ },
37
+ render: function() {
38
+ this.$el.html(this.template(this.model.toJSON()));
39
+ if (this.model.get('selected')) {
40
+ return this.$el.addClass('active');
41
+ } else {
42
+ return this.$el.removeClass('active');
43
+ }
44
+ }
45
+ });
46
+ PageTitlesView = Backbone.View.extend({
47
+ initialize: function() {
48
+ return pageInfos.bind('reset', this.render, this);
49
+ },
50
+ addOne: function(pageInfo) {
51
+ var view;
52
+ view = new PageTitleView({
53
+ model: pageInfo
54
+ });
55
+ view.render();
56
+ return $('#page-titles').append(view.el);
57
+ },
58
+ render: function() {
59
+ $('#page-titles').empty();
60
+ return pageInfos.each(this.addOne);
61
+ }
62
+ });
63
+ pageTitlesApp = new PageTitlesView;
64
+ pageInfos.reset(gon.pageInfos);
65
+ Widget = Backbone.Model.extend({
66
+ initialize: function() {
67
+ this.needRefresh = true;
68
+ return this.setNextFetch();
69
+ },
70
+ time: function() {
71
+ return (new Date()).getTime();
72
+ },
73
+ setNextFetch: function() {
74
+ return this.nextFetch = this.time() + this.get('interval') * 1000;
75
+ },
76
+ setRefresh: function(needRefresh) {
77
+ return this.needRefresh = needRefresh;
78
+ },
79
+ needFetch: function() {
80
+ var interval;
81
+ interval = this.get('interval');
82
+ return this.time() > this.nextFetch && this.needRefresh && (interval != null) && interval > 0;
83
+ },
84
+ refetch: function() {
85
+ if (this.needFetch()) {
86
+ this.fetch();
87
+ return this.setNextFetch();
88
+ }
89
+ },
90
+ cutoffValue: function(v, min, max) {
91
+ if (min !== null && v.y < min) {
92
+ v.y = min;
93
+ v.color = globalOptions.outlierColor;
94
+ }
95
+ if (max !== null && v.y > max) {
96
+ v.y = max;
97
+ return v.color = globalOptions.outlierColor;
98
+ }
99
+ },
100
+ cutoff: function(min, max) {
101
+ return _.each(this.get('series'), function(s) {
102
+ return _.each(s.data, function(v) {
103
+ return this.cutoffValue(v, min, max);
104
+ }, this);
105
+ }, this);
106
+ }
107
+ });
108
+ WidgetList = Backbone.Collection.extend({
109
+ model: Widget,
110
+ url: function() {
111
+ return ROOT + 'pages/' + pageInfos.selected().id + '/widgets';
112
+ }
113
+ });
114
+ WidgetChartView = Backbone.View.extend({
115
+ tagName: 'div',
116
+ initialize: function() {
117
+ return this.model.bind('destroy', this.remove, this);
118
+ },
119
+ updateData: function(min, max) {
120
+ var chartSeries, i, newSeries, _i, _ref;
121
+ this.model.cutoff(min, max);
122
+ chartSeries = this.chart.series;
123
+ newSeries = this.model.get('series');
124
+ for (i = _i = 0, _ref = chartSeries.length - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; i = 0 <= _ref ? ++_i : --_i) {
125
+ if (newSeries[i] != null) {
126
+ chartSeries[i].setData(newSeries[i].data, false);
127
+ }
128
+ }
129
+ return this.chart.redraw();
130
+ },
131
+ render: function() {
132
+ var options;
133
+ options = {
134
+ chart: {
135
+ renderTo: this.el,
136
+ plotBorderWidth: 1,
137
+ spacingLeft: 0,
138
+ spacingRight: 0,
139
+ type: this.model.get('type'),
140
+ animation: false,
141
+ zoomType: 'x'
142
+ },
143
+ credits: {
144
+ enabled: false
145
+ },
146
+ title: {
147
+ text: this.model.get('title')
148
+ },
149
+ xAxis: {
150
+ type: 'datetime'
151
+ },
152
+ yAxis: {
153
+ title: {
154
+ text: this.model.get('valuesTitle')
155
+ }
156
+ },
157
+ tooltip: {
158
+ xDateFormat: '%Y-%m-%d %H:%M:%S',
159
+ valueDecimals: 6
160
+ },
161
+ series: this.model.get('series'),
162
+ plotOptions: {
163
+ series: {
164
+ animation: false,
165
+ lineWidth: 1,
166
+ shadow: false,
167
+ marker: {
168
+ radius: 0
169
+ }
170
+ }
171
+ }
172
+ };
173
+ $.extend(true, options, globalOptions.highchartOptions, pageInfos.selected().get('highchartOptions'));
174
+ return this.chart = new Highcharts.Chart(options);
175
+ }
176
+ });
177
+ WidgetView = Backbone.View.extend({
178
+ tagName: 'div',
179
+ template: _.template($('#widget-template').html()),
180
+ initialize: function() {
181
+ this.model.bind('destroy', this.remove, this);
182
+ return this.model.bind('change', this.updateChart, this);
183
+ },
184
+ events: {
185
+ "click #refresh": 'refresh',
186
+ "click #need-refresh": 'setRefresh'
187
+ },
188
+ refresh: function() {
189
+ return this.model.fetch();
190
+ },
191
+ setRefresh: function() {
192
+ var needRefresh;
193
+ needRefresh = this.$el.find('#need-refresh').is(":checked");
194
+ this.model.setRefresh(needRefresh);
195
+ return true;
196
+ },
197
+ renderChart: function() {
198
+ return this.chartView.render();
199
+ },
200
+ updateChart: function() {
201
+ return this.chartView.updateData(this.cutoffMin(), this.cutoffMax());
202
+ },
203
+ render: function() {
204
+ this.$el.html(this.template(this.model.toJSON()));
205
+ this.chartView = new WidgetChartView({
206
+ model: this.model
207
+ });
208
+ this.$el.find("#plotarea").append(this.chartView.el);
209
+ return this.$el.addClass("span" + (this.model.get('width')));
210
+ },
211
+ cutoffMin: function() {
212
+ var val;
213
+ val = parseFloat(this.controlValue('#cutoff-min'));
214
+ if (_.isNaN(val)) {
215
+ return null;
216
+ } else {
217
+ return val;
218
+ }
219
+ },
220
+ cutoffMax: function() {
221
+ var val;
222
+ val = parseFloat(this.controlValue('#cutoff-max'));
223
+ if (_.isNaN(val)) {
224
+ return null;
225
+ } else {
226
+ return val;
227
+ }
228
+ },
229
+ controlValue: function(id) {
230
+ var val;
231
+ return val = this.$el.find(id).first().val();
232
+ }
233
+ });
234
+ widgetList = new WidgetList;
235
+ setInterval(function() {
236
+ return widgetList.each(function(w) {
237
+ return w.refetch();
238
+ });
239
+ }, 200);
240
+ WidgetListView = Backbone.View.extend({
241
+ initialize: function() {
242
+ return widgetList.bind('reset', this.render, this);
243
+ },
244
+ render: function() {
245
+ var container;
246
+ container = $('#widgets');
247
+ container.empty();
248
+ return widgetList.each(function(w) {
249
+ var view;
250
+ view = new WidgetView({
251
+ model: w
252
+ });
253
+ view.render();
254
+ container.append(view.el);
255
+ return view.renderChart();
256
+ });
257
+ }
258
+ });
259
+ widgetListApp = new WidgetListView;
260
+ AppRouter = Backbone.Router.extend({
261
+ routes: {
262
+ 'pages/:id': 'getPage',
263
+ '*actions': 'defaultRoute'
264
+ },
265
+ getPage: function(ids) {
266
+ var id;
267
+ id = parseInt(ids);
268
+ pageInfos.selectPage(id);
269
+ return widgetList.fetch();
270
+ },
271
+ defaultRoute: function(actions) {
272
+ if (pageInfos.length > 0) return this.navigate('//pages/1');
273
+ }
274
+ });
275
+ appRouter = new AppRouter;
276
+ return Backbone.history.start();
277
+ });
278
+
279
+ }).call(this);