pulse-meter 0.4.8 → 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +371 -353
- data/examples/full/client.rb +3 -2
- data/lib/pulse-meter.rb +1 -0
- data/lib/pulse-meter/command_aggregator/async.rb +1 -0
- data/lib/pulse-meter/mixins/utils.rb +13 -0
- data/lib/pulse-meter/observer.rb +18 -14
- data/lib/pulse-meter/observer/extended.rb +34 -0
- data/lib/pulse-meter/version.rb +1 -1
- data/lib/pulse-meter/visualize/public/css/application.css +6 -1
- data/lib/pulse-meter/visualize/views/main.haml +7 -10
- data/lib/pulse-meter/visualize/views/sensors.haml +5 -8
- data/lib/pulse-meter/visualize/views/widgets/area.haml +1 -1
- data/lib/pulse-meter/visualize/views/widgets/line.haml +1 -1
- data/lib/pulse-meter/visualize/views/widgets/table.haml +1 -1
- data/spec/pulse_meter/mixins/utils_spec.rb +8 -0
- data/spec/pulse_meter/observer/extended_spec.rb +92 -0
- data/spec/pulse_meter/observer_spec.rb +0 -45
- data/spec/support/observered.rb +40 -0
- metadata +7 -2
data/README.md
CHANGED
@@ -32,11 +32,13 @@ can be updated through client side objects associated with this data. The semant
|
|
32
32
|
different: some counter, value, series of values, etc. There is no need to care about explicit creation this data:
|
33
33
|
one just creates a client object and writes data to it, e.g.
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
```ruby
|
36
|
+
PulseMeter.redis = Redis.new
|
37
|
+
sensor = PulseMeter::Sensor::Counter.new :my_counter
|
38
|
+
sensor.event(5)
|
39
|
+
...
|
40
|
+
sensor.event(3)
|
41
|
+
```
|
40
42
|
|
41
43
|
After that the value associated with the counter is immediately available (through CLI, for example). Any other
|
42
44
|
client can access the associated counter by creating object with the same redis db and sensor name.
|
@@ -94,12 +96,14 @@ There are several caveats with timeline sensors:
|
|
94
96
|
Observer allows to notify sensors each time some class or instance method is called
|
95
97
|
Suppose you have a user model and want to count users distribytion by name. To do this you have to observe class method `create` of User class:
|
96
98
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
99
|
+
```ruby
|
100
|
+
sensors = PulseMeter::Sensor::Configuration.new(
|
101
|
+
users_by_name: {sensor_type: 'hashed_counter'}
|
102
|
+
)
|
103
|
+
PulseMeter::Observer.observe_class_method(User, :create, sensors) do |execution_time, attrs|
|
104
|
+
users_by_name({attrs[:name] => 1})
|
105
|
+
end
|
106
|
+
```
|
103
107
|
|
104
108
|
Each time the observed method is called, the block recieves all method's arguments prepended with method's execution time. Block is executed in context of the receiver object passed to observer (this means that `users_by_name` method refers to `sensors`).
|
105
109
|
One should use `observe_method` to observe instance methods.
|
@@ -110,135 +114,139 @@ One should use `observe_method` to observe instance methods.
|
|
110
114
|
|
111
115
|
Just create sensor objects and write data. Some examples below.
|
112
116
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
117
|
+
```ruby
|
118
|
+
require 'pulse-meter'
|
119
|
+
PulseMeter.redis = Redis.new
|
120
|
+
|
121
|
+
# static sensor examples
|
122
|
+
|
123
|
+
counter = PulseMeter::Sensor::Counter.new :my_counter
|
124
|
+
counter.event(1)
|
125
|
+
counter.event(2)
|
126
|
+
puts counter.value
|
127
|
+
# prints
|
128
|
+
# 3
|
129
|
+
|
130
|
+
indicator = PulseMeter::Sensor::Indicator.new :my_value
|
131
|
+
indicator.event(3.14)
|
132
|
+
indicator.event(2.71)
|
133
|
+
puts indicator.value
|
134
|
+
# prints
|
135
|
+
# 2.71
|
136
|
+
|
137
|
+
hashed_counter = PulseMeter::Sensor::HashedCounter.new :my_h_counter
|
138
|
+
hashed_counter.event(:x => 1)
|
139
|
+
hashed_counter.event(:y => 5)
|
140
|
+
hashed_counter.event(:y => 1)
|
141
|
+
p hashed_counter.value
|
142
|
+
# prints
|
143
|
+
# {"x"=>1, "y"=>6}
|
144
|
+
|
145
|
+
# timeline sensor examples
|
146
|
+
|
147
|
+
requests_per_minute = PulseMeter::Sensor::Timelined::Counter.new(:my_t_counter,
|
148
|
+
:interval => 60, # count for each minute
|
149
|
+
:ttl => 24 * 60 * 60 # keep data one day
|
150
|
+
)
|
151
|
+
requests_per_minute.event(1)
|
152
|
+
requests_per_minute.event(1)
|
153
|
+
sleep(60)
|
154
|
+
requests_per_minute.event(1)
|
155
|
+
requests_per_minute.timeline(2 * 60).each do |v|
|
156
|
+
puts "#{v.start_time}: #{v.value}"
|
157
|
+
end
|
158
|
+
# prints somewhat like
|
159
|
+
# 2012-05-24 11:06:00 +0400: 2
|
160
|
+
# 2012-05-24 11:07:00 +0400: 1
|
161
|
+
|
162
|
+
max_per_minute = PulseMeter::Sensor::Timelined::Max.new(:my_t_max,
|
163
|
+
:interval => 60, # max for each minute
|
164
|
+
:ttl => 24 * 60 * 60 # keep data one day
|
165
|
+
)
|
166
|
+
max_per_minute.event(3)
|
167
|
+
max_per_minute.event(1)
|
168
|
+
max_per_minute.event(2)
|
169
|
+
sleep(60)
|
170
|
+
max_per_minute.event(5)
|
171
|
+
max_per_minute.event(7)
|
172
|
+
max_per_minute.event(6)
|
173
|
+
max_per_minute.timeline(2 * 60).each do |v|
|
174
|
+
puts "#{v.start_time}: #{v.value}"
|
175
|
+
end
|
176
|
+
# prints somewhat like
|
177
|
+
# 2012-05-24 11:07:00 +0400: 3.0
|
178
|
+
# 2012-05-24 11:08:00 +0400: 7.0
|
179
|
+
```
|
174
180
|
|
175
181
|
There is also an alternative and a bit more DRY way for sensor creation, management and usage using `PulseMeter::Sensor::Configuration` class. It is also convenient for creating a bunch of sensors from some configuration data. Using and creating sensors through `PulseMeter::Sensor::Configuration` also allows to ignore any i/o errors (i.e. redis server unavailability), and this is generally the required case.
|
176
182
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
sensors.my_h_counter(:x => 1)
|
214
|
-
sensors.my_h_counter(:y => 5)
|
215
|
-
sensors.my_h_counter(:y => 1)
|
216
|
-
sensors.sensor(:my_h_counter) do |s|
|
217
|
-
p s.value
|
218
|
-
end
|
183
|
+
```ruby
|
184
|
+
require 'pulse-meter'
|
185
|
+
PulseMeter.redis = Redis.new
|
186
|
+
|
187
|
+
sensors = PulseMeter::Sensor::Configuration.new(
|
188
|
+
my_counter: {sensor_type: 'counter'},
|
189
|
+
my_value: {sensor_type: 'indicator'},
|
190
|
+
my_h_counter: {sensor_type: 'hashed_counter'},
|
191
|
+
my_t_counter: {
|
192
|
+
sensor_type: 'timelined/counter',
|
193
|
+
args: {
|
194
|
+
interval: 60, # count for each minute
|
195
|
+
ttl: 24 * 60 * 60 # keep data one day
|
196
|
+
}
|
197
|
+
},
|
198
|
+
my_t_max: {
|
199
|
+
sensor_type: 'timelined/max',
|
200
|
+
args: {
|
201
|
+
interval: 60, # count for each minute
|
202
|
+
ttl: 24 * 60 * 60 # keep data one day
|
203
|
+
}
|
204
|
+
}
|
205
|
+
)
|
206
|
+
|
207
|
+
sensors.my_counter(1)
|
208
|
+
sensors.my_counter(2)
|
209
|
+
sensors.sensor(:my_counter) do |s|
|
210
|
+
puts s.value
|
211
|
+
end
|
212
|
+
|
213
|
+
sensors.my_value(3.14)
|
214
|
+
sensors.my_value(2.71)
|
215
|
+
sensors.sensor(:my_value) do |s|
|
216
|
+
puts s.value
|
217
|
+
end
|
219
218
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
219
|
+
|
220
|
+
sensors.my_h_counter(:x => 1)
|
221
|
+
sensors.my_h_counter(:y => 5)
|
222
|
+
sensors.my_h_counter(:y => 1)
|
223
|
+
sensors.sensor(:my_h_counter) do |s|
|
224
|
+
p s.value
|
225
|
+
end
|
226
|
+
|
227
|
+
sensors.my_t_counter(1)
|
228
|
+
sensors.my_t_counter(1)
|
229
|
+
sleep(60)
|
230
|
+
sensors.my_t_counter(1)
|
231
|
+
sensors.sensor(:my_t_counter) do |s|
|
232
|
+
s.timeline(2 * 60).each do |v|
|
233
|
+
puts "#{v.start_time}: #{v.value}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
sensors.my_t_max(3)
|
238
|
+
sensors.my_t_max(1)
|
239
|
+
sensors.my_t_max(2)
|
240
|
+
sleep(60)
|
241
|
+
sensors.my_t_max(5)
|
242
|
+
sensors.my_t_max(7)
|
243
|
+
sensors.my_t_max(6)
|
244
|
+
sensors.sensor(:my_t_max) do |s|
|
245
|
+
s.timeline(2 * 60).each do |v|
|
246
|
+
puts "#{v.start_time}: #{v.value}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
242
250
|
|
243
251
|
## Command line interface
|
244
252
|
|
@@ -269,42 +277,46 @@ at project root and visit
|
|
269
277
|
|
270
278
|
`client.rb` just creates a timelined counter an sends data to it in an infinite loop.
|
271
279
|
|
272
|
-
|
280
|
+
```ruby
|
281
|
+
require "pulse-meter"
|
273
282
|
|
274
|
-
|
283
|
+
PulseMeter.redis = Redis.new
|
275
284
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
285
|
+
sensor = PulseMeter::Sensor::Timelined::Counter.new(:simple_sample_counter,
|
286
|
+
:interval => 5,
|
287
|
+
:ttl => 60 * 60
|
288
|
+
)
|
280
289
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
290
|
+
while true
|
291
|
+
STDERR.puts "tick"
|
292
|
+
sensor.event(1)
|
293
|
+
sleep(Random.rand)
|
294
|
+
end
|
295
|
+
```
|
286
296
|
|
287
297
|
`server.ru` is a Rackup file creating a simple layout with one page and one widget on it, which displays
|
288
298
|
the sensor's data. The layout is converted to a rack application and launched.
|
289
299
|
|
290
|
-
|
300
|
+
```ruby
|
301
|
+
require "pulse-meter/visualizer"
|
291
302
|
|
292
|
-
|
303
|
+
PulseMeter.redis = Redis.new
|
293
304
|
|
294
|
-
|
305
|
+
layout = PulseMeter::Visualizer.draw do |l|
|
295
306
|
|
296
|
-
|
307
|
+
l.title "Minimal App"
|
297
308
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
309
|
+
l.page "Main Page" do |p|
|
310
|
+
p.area "Live Counter",
|
311
|
+
sensor: :simple_sample_counter,
|
312
|
+
timespan: 5 * 60,
|
313
|
+
redraw_interval: 1
|
314
|
+
end
|
304
315
|
|
305
|
-
|
316
|
+
end
|
306
317
|
|
307
|
-
|
318
|
+
run layout.to_app
|
319
|
+
```
|
308
320
|
|
309
321
|
`Procfile` allows to launch both "client" script and the web server with `foreman`.
|
310
322
|
|
@@ -320,243 +332,249 @@ at project root and visit
|
|
320
332
|
|
321
333
|
`client.rb` imitating users visiting some imaginary site.
|
322
334
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
335
|
+
```ruby
|
336
|
+
require "pulse-meter"
|
337
|
+
|
338
|
+
PulseMeter.redis = Redis.new
|
339
|
+
|
340
|
+
sensors = PulseMeter::Sensor::Configuration.new(
|
341
|
+
requests_per_minute: {
|
342
|
+
sensor_type: 'timelined/counter',
|
343
|
+
args: {
|
344
|
+
annotation: 'Requests per minute',
|
345
|
+
interval: 60,
|
346
|
+
ttl: 60 * 60 * 24 # keep data one day
|
347
|
+
}
|
348
|
+
},
|
349
|
+
requests_per_hour: {
|
350
|
+
sensor_type: 'timelined/counter',
|
351
|
+
args: {
|
352
|
+
annotation: 'Requests per hour',
|
353
|
+
interval: 60 * 60,
|
354
|
+
ttl: 60 * 60 * 24 * 30 # keep data 30 days
|
355
|
+
}
|
356
|
+
},
|
357
|
+
# when ActiveSupport extentions are loaded, a better way is to write just
|
358
|
+
# :interval => 1.hour,
|
359
|
+
# :ttl => 30.days
|
360
|
+
errors_per_minute: {
|
361
|
+
sensor_type: 'timelined/counter',
|
362
|
+
args: {
|
363
|
+
annotation: 'Errors per minute',
|
364
|
+
interval: 60,
|
365
|
+
ttl: 60 * 60 * 24
|
366
|
+
}
|
367
|
+
},
|
368
|
+
errors_per_hour: {
|
369
|
+
sensor_type: 'timelined/counter',
|
370
|
+
args: {
|
371
|
+
annotation: 'Errors per hour',
|
372
|
+
interval: 60 * 60,
|
373
|
+
ttl: 60 * 60 * 24 * 30
|
374
|
+
}
|
375
|
+
},
|
376
|
+
longest_minute_request: {
|
377
|
+
sensor_type: 'timelined/max',
|
378
|
+
args: {
|
379
|
+
annotation: 'Longest minute requests',
|
380
|
+
interval: 60,
|
381
|
+
ttl: 60 * 60 * 24
|
382
|
+
}
|
383
|
+
},
|
384
|
+
shortest_minute_request: {
|
385
|
+
sensor_type: 'timelined/min',
|
386
|
+
args: {
|
387
|
+
annotation: 'Shortest minute requests',
|
388
|
+
interval: 60,
|
389
|
+
ttl: 60 * 60 * 24
|
390
|
+
}
|
391
|
+
},
|
392
|
+
perc90_minute_request: {
|
393
|
+
sensor_type: 'timelined/percentile',
|
394
|
+
args: {
|
395
|
+
annotation: 'Minute request 90-percent percentile',
|
396
|
+
interval: 60,
|
397
|
+
ttl: 60 * 60 * 24,
|
398
|
+
p: 0.9
|
399
|
+
}
|
400
|
+
},
|
401
|
+
cpu: {
|
402
|
+
sensor_type: 'indicator',
|
403
|
+
args: {
|
404
|
+
annotation: 'CPU%'
|
405
|
+
}
|
406
|
+
}
|
407
|
+
)
|
408
|
+
|
409
|
+
agent_names = [:ie, :firefox, :chrome, :other]
|
410
|
+
agent_names.each do |agent|
|
411
|
+
sensors.add_sensor(agent,
|
412
|
+
sensor_type: 'timelined/counter',
|
413
|
+
args: {
|
414
|
+
annotation: "Requests from #{agent} browser",
|
415
|
+
interval: 60 * 60,
|
416
|
+
ttl: 60 * 60 * 24 * 30
|
417
|
+
}
|
418
|
+
)
|
419
|
+
end
|
420
|
+
|
421
|
+
while true
|
422
|
+
sensors.requests_per_minute(1)
|
423
|
+
sensors.requests_per_hour(1)
|
424
|
+
|
425
|
+
if Random.rand(10) < 1 # let "errors" sometimes occur
|
426
|
+
sensors.errors_per_minute(1)
|
427
|
+
sensors.errors_per_hour(1)
|
428
|
+
end
|
429
|
+
|
430
|
+
request_time = 0.1 + Random.rand
|
431
|
+
|
432
|
+
sensors.longest_minute_request(request_time)
|
433
|
+
sensors.shortest_minute_request(request_time)
|
434
|
+
sensors.perc90_minute_request(request_time)
|
435
|
+
|
436
|
+
agent_counter = sensors.sensor(agent_names.shuffle.first)
|
437
|
+
agent_counter.event(1)
|
438
|
+
|
439
|
+
sensors.cpu(Random.rand(100))
|
440
|
+
|
441
|
+
sleep(Random.rand / 10)
|
442
|
+
end
|
443
|
+
```
|
430
444
|
|
431
445
|
A more complicated visualization
|
432
446
|
|
433
|
-
|
447
|
+
```ruby
|
448
|
+
require "pulse-meter/visualizer"
|
434
449
|
|
435
|
-
|
450
|
+
PulseMeter.redis = Redis.new
|
436
451
|
|
437
|
-
|
452
|
+
layout = PulseMeter::Visualizer.draw do |l|
|
438
453
|
|
439
|
-
|
440
|
-
|
454
|
+
# Application title
|
455
|
+
l.title "Full Example"
|
441
456
|
|
442
|
-
|
443
|
-
|
457
|
+
# Use local time for x-axis of charts
|
458
|
+
l.use_utc false
|
444
459
|
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
460
|
+
# Transfer some global parameters to Google Charts
|
461
|
+
l.gchart_options({
|
462
|
+
background_color: '#CCC'
|
463
|
+
})
|
449
464
|
|
450
|
-
|
451
|
-
|
465
|
+
# Add some pages
|
466
|
+
l.page "Request count" do |p|
|
452
467
|
|
453
|
-
|
454
|
-
|
468
|
+
# Add chart (of Google Charts `area' style, `pie' and `line' are also available)
|
469
|
+
p.area "Requests per minute" do |w|
|
455
470
|
|
456
|
-
|
457
|
-
|
471
|
+
# Plot :requests_per_minute values on this chart with black color
|
472
|
+
w.sensor :requests_per_minute, color: '#000000'
|
458
473
|
|
459
|
-
|
460
|
-
|
474
|
+
# Plot :errors_per_minute values on this chart with red color
|
475
|
+
w.sensor :errors_per_minute, color: '#FF0000'
|
461
476
|
|
462
|
-
|
463
|
-
|
477
|
+
# Plot values for the last hour
|
478
|
+
w.timespan 60 * 60
|
464
479
|
|
465
|
-
|
466
|
-
|
480
|
+
# Redraw chart every 10 seconds
|
481
|
+
w.redraw_interval 10
|
467
482
|
|
468
|
-
|
469
|
-
|
483
|
+
# Plot incomplete data
|
484
|
+
w.show_last_point true
|
470
485
|
|
471
|
-
|
472
|
-
|
486
|
+
# Meaning of the y-axis
|
487
|
+
w.values_label "Request count"
|
473
488
|
|
474
|
-
|
475
|
-
|
489
|
+
# Occupy half (5/10) of the page (horizontally)
|
490
|
+
w.width 5
|
476
491
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
492
|
+
# Transfer page-wide (and page-specific) options to Google Charts
|
493
|
+
p.gchart_options({
|
494
|
+
height: 500
|
495
|
+
})
|
496
|
+
end
|
482
497
|
|
483
|
-
|
498
|
+
p.area "Requests per hour" do |w|
|
484
499
|
|
485
|
-
|
486
|
-
|
500
|
+
w.sensor :requests_per_hour, color: '#555555'
|
501
|
+
w.sensor :errors_per_hour, color: '#FF0000'
|
487
502
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
503
|
+
w.timespan 24 * 60 * 60
|
504
|
+
w.redraw_interval 10
|
505
|
+
w.show_last_point true
|
506
|
+
w.values_label "Request count"
|
507
|
+
w.width 5
|
493
508
|
|
494
|
-
|
495
|
-
|
509
|
+
end
|
510
|
+
end
|
496
511
|
|
497
|
-
|
498
|
-
|
512
|
+
l.page "Request times" do |p|
|
513
|
+
p.area "Requests time" do |w|
|
499
514
|
|
500
|
-
|
501
|
-
|
502
|
-
|
515
|
+
w.sensor :longest_minute_request
|
516
|
+
w.sensor :shortest_minute_request
|
517
|
+
w.sensor :perc90_minute_request
|
503
518
|
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
519
|
+
w.timespan 60 * 60
|
520
|
+
w.redraw_interval 10
|
521
|
+
w.show_last_point true
|
522
|
+
w.values_label "Time in seconds"
|
523
|
+
w.width 10
|
509
524
|
|
510
|
-
|
511
|
-
|
525
|
+
end
|
526
|
+
end
|
512
527
|
|
513
|
-
|
514
|
-
|
528
|
+
l.page "Browsers" do |p|
|
529
|
+
p.pie "Requests from browser" do |w|
|
515
530
|
|
516
|
-
|
517
|
-
|
518
|
-
|
531
|
+
[:ie, :firefox, :chrome, :other].each do |sensor|
|
532
|
+
w.sensor sensor
|
533
|
+
end
|
519
534
|
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
535
|
+
w.redraw_interval 10
|
536
|
+
w.show_last_point true
|
537
|
+
w.values_label "Request count"
|
538
|
+
w.width 10
|
524
539
|
|
525
|
-
|
540
|
+
end
|
526
541
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
542
|
+
p.gchart_options({
|
543
|
+
height: 500
|
544
|
+
})
|
545
|
+
end
|
531
546
|
|
532
|
-
|
547
|
+
l.page "Gauge" do |p|
|
533
548
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
549
|
+
p.gauge "CPU Load" do |g|
|
550
|
+
g.redraw_interval 5
|
551
|
+
g.values_label '%'
|
552
|
+
g.width 5
|
538
553
|
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
554
|
+
g.red_from 90
|
555
|
+
g.red_to 100
|
556
|
+
g.yellow_from 75
|
557
|
+
g.yellow_to 90
|
558
|
+
g.minor_ticks 5
|
559
|
+
g.height 200
|
545
560
|
|
546
|
-
|
547
|
-
|
561
|
+
g.sensor :cpu
|
562
|
+
end
|
548
563
|
|
549
|
-
|
564
|
+
end
|
550
565
|
|
551
|
-
|
566
|
+
end
|
552
567
|
|
553
|
-
|
568
|
+
run layout.to_app
|
569
|
+
```
|
554
570
|
|
555
571
|
## Installation
|
556
572
|
|
557
573
|
Add this line to your application's Gemfile:
|
558
574
|
|
559
|
-
|
575
|
+
```ruby
|
576
|
+
gem 'pulse-meter'
|
577
|
+
```
|
560
578
|
|
561
579
|
And then execute:
|
562
580
|
|