pulse-meter 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.sw?
19
+ /.idea
data/.rbenv-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.2-p290
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color -fd
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pulse-meter.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Ilya Averyanov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1,3 @@
1
+ watch_coffee: bundle exec rake coffee:watch
2
+ web_sample: bundle exec rackup examples/basic.ru
3
+ sensor_sample: bundle exec ruby examples/basic_sensor_data.rb
data/README.md ADDED
@@ -0,0 +1,440 @@
1
+ [![Build Status](https://secure.travis-ci.org/savonarola/pulse-meter.png)](http://travis-ci.org/savonarola/pulse-meter)
2
+
3
+ # PulseMeter
4
+
5
+ PulseMeter is a gem for fast and convenient realtime aggregating of software internal stats through Redis.
6
+
7
+ ## Features
8
+
9
+ PulseMeter is designed to provide the following features:
10
+
11
+ * Simple deployment. The only infrastructure resource you are required to have is Redis.
12
+
13
+ * Low resource consumption. Since different kinds of events are aggregated in Redis,
14
+ you are as light and fast as Redis is.
15
+ Event data is stored in constant space and expires over time.
16
+
17
+ * Focus on the client. To start gathering some metrics, you should only modify your client: create a sensor object
18
+ and send events to it. All aggregated data can be accessed immediately without any
19
+ sort of "server reconfiguration"
20
+
21
+ ## Concept
22
+
23
+ The fundamental concept of PulseMeter is *sensor*. Sensor is some named piece of data in Redis which
24
+ can be updated through client side objects associated with this data. The semantics of the data can be
25
+ different: some counter, value, series of values, etc. There is no need to care about explicit creation this data:
26
+ one just creates a client object and writes data to it, e.g.
27
+
28
+ PulseMeter.redis = Redis.new
29
+ sensor = PulseMeter::Sensor::Counter.new :my_counter
30
+ sensor.event(5)
31
+ ...
32
+ sensor.event(3)
33
+
34
+ After that the value associated with the counter is immediately available (through CLI, for example). Any other
35
+ client can access the associated counter by creating object with the same redis db and sensor name.
36
+
37
+ Sensors can be divided into two large groups.
38
+
39
+ ### Static sensors
40
+
41
+ These are just single values which can be read by CLI, e.g. some counter or some value
42
+ representing current state of a resource (current free memory amount, current la etc.). Currently, the
43
+ following static sensors are available:
44
+
45
+ * Counter
46
+ * Hashed Counter
47
+ * Indicator
48
+
49
+ They have no web visualisation interface and they are assumed to be used by external visualisation tools.
50
+
51
+
52
+ ### Timeline sensors
53
+
54
+ These sensors are series of values, one value for each consequent time interval. They
55
+ are available by CLI and have web visualisation interface. Examples of such sensors include: count of
56
+ requests to some resource per hour, the longest request to a database per minute, etc.
57
+
58
+ The following timeline sensors are available:
59
+
60
+ * Average value
61
+ * Counter
62
+ * Hashed counter
63
+ * Max value
64
+ * Min value
65
+ * Median value
66
+ * Percentile
67
+
68
+ There are several caveats with timeline sensors:
69
+
70
+ * The value of a sensor for the last interval (which is not finished yet) is often not very useful.
71
+ When building a visualisation you may choose to display the last value or not.
72
+ * For some sensors (currently Median and Percentile) considerable amount of data should be stored for a
73
+ particular interval to obtain value for this interval. So it is a good idea to schedule
74
+ <tt>pulse reduce</tt>
75
+ command on a regular basis. This command reduces the stored data for passed intervals to single values,
76
+ so that they do not consume storage space.
77
+
78
+ ## Client usage
79
+
80
+ Just create sensor objects and write data. Some examples below.
81
+
82
+ require 'pulse-meter'
83
+ PulseMeter.redis = Redis.new
84
+
85
+ # static sensor examples
86
+
87
+ counter = PulseMeter::Sensor::Counter.new :my_counter
88
+ counter.event(1)
89
+ counter.event(2)
90
+ puts counter.value
91
+ # prints
92
+ # 3
93
+
94
+ indicator = PulseMeter::Sensor::Indicator.new :my_value
95
+ indicator.event(3.14)
96
+ indicator.event(2.71)
97
+ puts indicator.value
98
+ # prints
99
+ # 2.71
100
+
101
+ hashed_counter = PulseMeter::Sensor::HashedCounter.new :my_h_counter
102
+ hashed_counter.event(:x => 1)
103
+ hashed_counter.event(:y => 5)
104
+ hashed_counter.event(:y => 1)
105
+ p hashed_counter.value
106
+ # prints
107
+ # {"x"=>1, "y"=>6}
108
+
109
+ # timeline sensor examples
110
+
111
+ requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:my_t_counter,
112
+ :interval => 60, # count for each minute
113
+ :ttl => 24 * 60 * 60 # keep data one day
114
+ )
115
+ requests_per_minute.event(1)
116
+ requests_per_minute.event(1)
117
+ sleep(60)
118
+ requests_per_minute.event(1)
119
+ requests_per_minute.timeline(2 * 60).each do |v|
120
+ puts "#{v.start_time}: #{v.value}"
121
+ end
122
+ # prints somewhat like
123
+ # 2012-05-24 11:06:00 +0400: 2
124
+ # 2012-05-24 11:07:00 +0400: 1
125
+
126
+ max_per_minute = PulseMeter::Sensor::Timelined::Max.new(:my_t_max,
127
+ :interval => 60, # max for each minute
128
+ :ttl => 24 * 60 * 60 # keep data one day
129
+ )
130
+ max_per_minute.event(3)
131
+ max_per_minute.event(1)
132
+ max_per_minute.event(2)
133
+ sleep(60)
134
+ max_per_minute.event(5)
135
+ max_per_minute.event(7)
136
+ max_per_minute.event(6)
137
+ max_per_minute.timeline(2 * 60).each do |v|
138
+ puts "#{v.start_time}: #{v.value}"
139
+ end
140
+ # prints somewhat like
141
+ # 2012-05-24 11:07:00 +0400: 3.0
142
+ # 2012-05-24 11:08:00 +0400: 7.0
143
+
144
+ ## Command line interface
145
+
146
+ Gem includes a tool <tt>pulse</tt>, which allows to send events to sensors, list them, etc.
147
+ You should pay attention to the command <tt>pulse reduce</tt>, which is generally should be
148
+ scheduled on a regular basis to keep data in Redis small.
149
+
150
+ To see available commands of this tool one can run the example above(see <tt>examples/readme\_client\_example.rb</tt>)
151
+ and run <tt>pulse help</tt>.
152
+
153
+ ## Visualisation
154
+
155
+ PulseMeter comes with a simple DSL which allows to build a self-contained Rack application for
156
+ visualizing timeline sensor data.
157
+
158
+ The application is described by *Layout* which contains some general application options and a list of *Pages*.
159
+ Each page contain a list of *Widgets* (charts), and each widget is associated with several sensors, which produce
160
+ data series for the chart.
161
+
162
+ There is a minimal and a full example below.
163
+
164
+ ### Minimal example
165
+
166
+ It can be found in <tt>examples/minimal</tt> folder. To run it, execute
167
+ <tt>bundle && cd examples/minimal && bundle exec foreman start</tt> (or just <tt>rake example:minimal</tt>)
168
+ at project root and visit
169
+ <tt>http://localhost:9292</tt> at your browser.
170
+
171
+ <tt>client.rb</tt> just creates a timelined counter an sends data to it in an infinite loop.
172
+
173
+ require "pulse-meter"
174
+
175
+ PulseMeter.redis = Redis.new
176
+
177
+ sensor = PulseMeter::Sensor::Timelined::Counter.new(:simple_sample_counter,
178
+ :interval => 5,
179
+ :ttl => 60 * 60
180
+ )
181
+
182
+ while true
183
+ STDERR.puts "tick"
184
+ sensor.event(1)
185
+ sleep(Random.rand)
186
+ end
187
+
188
+ <tt>server.ru</tt> is a Rackup file creating a simple layout with one page and one widget on it, which displays
189
+ the sensor's data. The layout is converted to a rack application and launched.
190
+
191
+ require "pulse-meter/visualizer"
192
+
193
+ PulseMeter.redis = Redis.new
194
+
195
+ layout = PulseMeter::Visualizer.draw do |l|
196
+
197
+ l.title "Minimal App"
198
+
199
+ l.page "Main Page" do |p|
200
+ p.area "Live Counter",
201
+ sensor: :simple_sample_counter,
202
+ timespan: 5 * 60,
203
+ redraw_interval: 1
204
+ end
205
+
206
+ end
207
+
208
+ run layout.to_app
209
+
210
+ <tt>Procfile</tt> allows to launch both "client" script and the web server with <tt>foreman</tt>.
211
+
212
+ web: bundle exec rackup server.ru
213
+ sensor_data_generator: bundle exec ruby client.rb
214
+
215
+ ### Full example with DSL explanation
216
+
217
+ It can be found in <tt>examples/full</tt> folder. To run it, execute
218
+ <tt>bundle && cd examples/full && bundle exec foreman start</tt> (or just <tt>rake example:full</tt>)
219
+ at project root and visit
220
+ <tt>http://localhost:9292</tt> at your browser.
221
+
222
+ <tt>client.rb</tt> imitating users visiting some imaginary site
223
+
224
+ require "pulse-meter"
225
+
226
+ PulseMeter.redis = Redis.new
227
+
228
+ requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:requests_per_minute,
229
+ :annotation => 'Requests per minute',
230
+ :interval => 60,
231
+ :ttl => 60 * 60 * 24 # keep data one day
232
+ )
233
+
234
+ requests_per_hour = PulseMeter::Sensor::Timelined::Counter.new(:requests_per_hour,
235
+ :annotation => 'Requests per hour',
236
+ :interval => 60 * 60,
237
+ :ttl => 60 * 60 * 24 * 30 # keep data 30 days
238
+ # when ActiveSupport extentions are loaded, a better way is to write just
239
+ # :interval => 1.hour,
240
+ # :ttl => 30.days
241
+ )
242
+
243
+ errors_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:errors_per_minute,
244
+ :annotation => 'Errors per minute',
245
+ :interval => 60,
246
+ :ttl => 60 * 60 * 24
247
+ )
248
+
249
+ errors_per_hour = PulseMeter::Sensor::Timelined::Counter.new(:errors_per_hour,
250
+ :annotation => 'Errors per hour',
251
+ :interval => 60 * 60,
252
+ :ttl => 60 * 60 * 24 * 30
253
+ )
254
+
255
+ longest_minute_request = PulseMeter::Sensor::Timelined::Max.new(:longest_minute_request,
256
+ :annotation => 'Longest minute requests',
257
+ :interval => 60,
258
+ :ttl => 60 * 60 * 24
259
+ )
260
+
261
+ shortest_minute_request = PulseMeter::Sensor::Timelined::Min.new(:shortest_minute_request,
262
+ :annotation => 'Shortest minute requests',
263
+ :interval => 60,
264
+ :ttl => 60 * 60 * 24
265
+ )
266
+
267
+ perc90_minute_request = PulseMeter::Sensor::Timelined::Percentile.new(:perc90_minute_request,
268
+ :annotation => 'Minute request 90-percent percentile',
269
+ :interval => 60,
270
+ :ttl => 60 * 60 * 24,
271
+ :p => 0.9
272
+ )
273
+
274
+ agent_names = [:ie, :firefox, :chrome, :other]
275
+ hour_agents = agent_names.each_with_object({}) do |agent, h|
276
+ h[agent] = PulseMeter::Sensor::Timelined::Counter.new(agent,
277
+ :annotation => "Requests from #{agent} browser",
278
+ :interval => 60 * 60,
279
+ :ttl => 60 * 60 * 24 * 30
280
+ )
281
+ end
282
+
283
+
284
+ while true
285
+ requests_per_minute.event(1)
286
+ requests_per_hour.event(1)
287
+
288
+ if Random.rand(10) < 1 # let "errors" sometimes occur
289
+ errors_per_minute.event(1)
290
+ errors_per_hour.event(1)
291
+ end
292
+
293
+ request_time = 0.1 + Random.rand
294
+
295
+ longest_minute_request.event(request_time)
296
+ shortest_minute_request.event(request_time)
297
+ perc90_minute_request.event(request_time)
298
+
299
+ agent_counter = hour_agents[agent_names.shuffle.first]
300
+ agent_counter.event(1)
301
+
302
+ sleep(Random.rand / 10)
303
+ end
304
+
305
+ A more complicated visualization
306
+
307
+ require "pulse-meter/visualizer"
308
+
309
+ PulseMeter.redis = Redis.new
310
+
311
+ layout = PulseMeter::Visualizer.draw do |l|
312
+
313
+ # Application title
314
+ l.title "Full Example"
315
+
316
+ # Use local time for x-axis of charts
317
+ l.use_utc false
318
+
319
+ # Color for values cut off
320
+ l.outlier_color '#FF0000'
321
+
322
+ # Transfer some global parameters to Highcharts
323
+ l.highchart_options({
324
+ tooltip: {
325
+ value_decimals: 2
326
+ }
327
+ })
328
+
329
+ # Add some pages
330
+ l.page "Request count" do |p|
331
+
332
+ # Add chart (of Highcharts `area' style, `spline', `pie' and `line' are also available)
333
+ p.area "Requests per minute" do |w|
334
+
335
+ # Plot :requests_per_minute values on this chart with black color
336
+ w.sensor :requests_per_minute, color: '#000000'
337
+
338
+ # Plot :errors_per_minute values on this chart with red color
339
+ w.sensor :errors_per_minute, color: '#FF0000'
340
+
341
+ # Plot values for the last hour
342
+ w.timespan 60 * 60
343
+
344
+ # Redraw chart every 10 seconds
345
+ w.redraw_interval 10
346
+
347
+ # Plot incomplete data
348
+ w.show_last_point true
349
+
350
+ # Meaning of the y-axis
351
+ w.values_label "Request count"
352
+
353
+ # Occupy half (5/10) of the page (horizontally)
354
+ w.width 5
355
+
356
+ # Transfer page-wide (and page-specific) options to Highcharts
357
+ p.highchart_options({
358
+ chart: {
359
+ height: 300
360
+ }
361
+ })
362
+ end
363
+
364
+ p.area "Requests per hour" do |w|
365
+
366
+ w.sensor :requests_per_hour, color: '#555555'
367
+ w.sensor :errors_per_hour, color: '#FF0000'
368
+
369
+ w.timespan 24 * 60 * 60
370
+ w.redraw_interval 10
371
+ w.show_last_point true
372
+ w.values_label "Request count"
373
+ w.width 5
374
+
375
+ end
376
+ end
377
+
378
+ l.page "Request times" do |p|
379
+ p.area "Requests time" do |w|
380
+
381
+ w.sensor :longest_minute_request
382
+ w.sensor :shortest_minute_request
383
+ w.sensor :perc90_minute_request
384
+
385
+ w.timespan 60 * 60
386
+ w.redraw_interval 10
387
+ w.show_last_point true
388
+ w.values_label "Time in seconds"
389
+ w.width 10
390
+
391
+ end
392
+ end
393
+
394
+ l.page "Browsers" do |p|
395
+ p.pie "Requests from browser" do |w|
396
+
397
+ [:ie, :firefox, :chrome, :other].each do |sensor|
398
+ w.sensor sensor
399
+ end
400
+
401
+ w.timespan 24 * 60 * 60
402
+ w.redraw_interval 10
403
+ w.show_last_point true
404
+ w.values_label "Request count"
405
+ w.width 10
406
+
407
+ end
408
+
409
+ p.highchart_options({
410
+ chart: {
411
+ height: 500
412
+ }
413
+ })
414
+ end
415
+
416
+ end
417
+
418
+ run layout.to_app
419
+
420
+ ## Installation
421
+
422
+ Add this line to your application's Gemfile:
423
+
424
+ gem 'pulse-meter'
425
+
426
+ And then execute:
427
+
428
+ $ bundle
429
+
430
+ Or install it yourself as:
431
+
432
+ $ gem install pulse-meter
433
+
434
+ ## Contributing
435
+
436
+ 1. Fork it
437
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
438
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
439
+ 4. Push to the branch (`git push origin my-new-feature`)
440
+ 5. Create new Pull Request