pulse-meter 0.2.11 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rbenv-version +1 -1
- data/README.md +32 -16
- data/Rakefile +29 -10
- data/examples/basic.ru +1 -1
- data/examples/full/server.ru +1 -1
- data/examples/minimal/server.ru +1 -1
- data/lib/pulse-meter.rb +1 -0
- data/lib/pulse-meter/observer.rb +117 -0
- data/lib/pulse-meter/sensor.rb +1 -0
- data/lib/pulse-meter/sensor/timeline.rb +3 -2
- data/lib/pulse-meter/sensor/timelined/multi_percentile.rb +43 -0
- data/lib/pulse-meter/version.rb +1 -1
- data/lib/pulse-meter/visualize/app.rb +28 -12
- data/lib/pulse-meter/visualize/coffee/application.coffee +40 -0
- data/lib/pulse-meter/visualize/coffee/collections/page_info_list.coffee +17 -0
- data/lib/pulse-meter/visualize/coffee/collections/sensor_info_list.coffee +4 -0
- data/lib/pulse-meter/visualize/coffee/collections/widget_list.coffee +14 -0
- data/lib/pulse-meter/visualize/coffee/extensions.coffee +26 -0
- data/lib/pulse-meter/visualize/coffee/models/dinamic_widget.coffee +34 -0
- data/lib/pulse-meter/visualize/coffee/models/page_info.coffee +2 -0
- data/lib/pulse-meter/visualize/coffee/models/sensor_info.coffee +2 -0
- data/lib/pulse-meter/visualize/coffee/models/widget.coffee +54 -0
- data/lib/pulse-meter/visualize/coffee/presenters/area.coffee +2 -0
- data/lib/pulse-meter/visualize/coffee/presenters/gauge.coffee +11 -0
- data/lib/pulse-meter/visualize/coffee/presenters/line.coffee +2 -0
- data/lib/pulse-meter/visualize/coffee/presenters/pie.coffee +20 -0
- data/lib/pulse-meter/visualize/coffee/presenters/series.coffee +44 -0
- data/lib/pulse-meter/visualize/coffee/presenters/table.coffee +10 -0
- data/lib/pulse-meter/visualize/coffee/presenters/timeline.coffee +13 -0
- data/lib/pulse-meter/visualize/coffee/presenters/widget.coffee +65 -0
- data/lib/pulse-meter/visualize/coffee/router.coffee +21 -0
- data/lib/pulse-meter/visualize/coffee/views/dynamic_chart.coffee +91 -0
- data/lib/pulse-meter/visualize/coffee/views/dynamic_widget.coffee +58 -0
- data/lib/pulse-meter/visualize/coffee/views/page_title.coffee +17 -0
- data/lib/pulse-meter/visualize/coffee/views/page_titles.coffee +15 -0
- data/lib/pulse-meter/visualize/coffee/views/sensor_info_list.coffee +19 -0
- data/lib/pulse-meter/visualize/coffee/views/widget.coffee +99 -0
- data/lib/pulse-meter/visualize/coffee/views/widget_chart.coffee +13 -0
- data/lib/pulse-meter/visualize/coffee/views/widget_list.coffee +15 -0
- data/lib/pulse-meter/visualize/layout.rb +4 -4
- data/lib/pulse-meter/visualize/public/css/application.css +13 -4
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_ffffff_1x400.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_222222_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_2e83ff_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_454545_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_888888_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/images/ui-icons_f6cf3b_256x240.png +0 -0
- data/lib/pulse-meter/visualize/public/css/jquery-ui-1.8.16.bootstrap.css +1320 -0
- data/lib/pulse-meter/visualize/public/js/application.js +900 -691
- data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.16.bootstrap.min.js +791 -0
- data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.23.custom.min.js +21 -0
- data/lib/pulse-meter/visualize/public/js/jquery-ui-timepicker-addon.js +1687 -0
- data/lib/pulse-meter/visualize/sensor.rb +2 -2
- data/lib/pulse-meter/visualize/views/main.haml +2 -3
- data/lib/pulse-meter/visualize/views/sensors.haml +14 -1
- data/lib/pulse-meter/visualize/views/widgets/area.haml +46 -24
- data/lib/pulse-meter/visualize/views/widgets/line.haml +46 -23
- data/lib/pulse-meter/visualize/views/widgets/table.haml +37 -15
- data/lib/pulse-meter/visualize/widgets/timeline.rb +20 -5
- data/pulse-meter.gemspec +4 -0
- data/spec/pulse_meter/observer_spec.rb +252 -0
- data/spec/pulse_meter/sensor/timelined/multi_percentile_spec.rb +21 -0
- data/spec/pulse_meter/visualize/sensor_spec.rb +5 -5
- data/spec/pulse_meter/visualize/widgets/area_spec.rb +1 -74
- data/spec/pulse_meter/visualize/widgets/line_spec.rb +1 -73
- data/spec/pulse_meter/visualize/widgets/table_spec.rb +1 -73
- data/spec/shared_examples/timeline_sensor.rb +10 -0
- data/spec/shared_examples/widget.rb +97 -0
- data/spec/spec_helper.rb +1 -0
- metadata +120 -5
- data/lib/pulse-meter/visualize/public/js/application.coffee +0 -616
@@ -23,9 +23,9 @@ module PulseMeter
|
|
23
23
|
extractor.point_data(last_value(now, need_incomplete))
|
24
24
|
end
|
25
25
|
|
26
|
-
def timeline_data(
|
26
|
+
def timeline_data(from, till, need_incomplete = false)
|
27
27
|
sensor = real_sensor
|
28
|
-
timeline_data = sensor.timeline_within(
|
28
|
+
timeline_data = sensor.timeline_within(from, till)
|
29
29
|
timeline_data.pop unless need_incomplete
|
30
30
|
extractor.series_data(timeline_data)
|
31
31
|
end
|
@@ -4,9 +4,9 @@
|
|
4
4
|
:javascript
|
5
5
|
var ROOT = "#{url('/')}";
|
6
6
|
= include_gon
|
7
|
-
- %w{jquery-1.7.2.min.js json2.js underscore-min.js backbone-min.js application.js bootstrap.js}.each do |jsfile|
|
7
|
+
- %w{jquery-1.7.2.min.js jquery-ui-1.8.16.bootstrap.min.js jquery-ui-timepicker-addon.js json2.js underscore-min.js backbone-min.js application.js bootstrap.js}.each do |jsfile|
|
8
8
|
%script{type: 'text/javascript', src: url("/js/#{jsfile}")}
|
9
|
-
- %w{bootstrap.min.css application.css}.each do |cssfile|
|
9
|
+
- %w{bootstrap.min.css application.css jquery-ui-1.8.16.bootstrap.css}.each do |cssfile|
|
10
10
|
%link{rel: 'stylesheet', href: url("/css/#{cssfile}"), type: 'text/css', media: 'screen'}
|
11
11
|
%script{type: 'text/javascript', src: "https://www.google.com/jsapi"}
|
12
12
|
:javascript
|
@@ -16,7 +16,6 @@
|
|
16
16
|
document.startApp();
|
17
17
|
}
|
18
18
|
%body
|
19
|
-
|
20
19
|
- %w(area line table pie gauge).each do |wtype|
|
21
20
|
= partial "widgets/#{wtype}"
|
22
21
|
= partial "sensors"
|
@@ -19,7 +19,7 @@
|
|
19
19
|
|
20
20
|
%script#dynamic-widget-error{type: 'text/template'}
|
21
21
|
.alert.alert-error
|
22
|
-
.button.close{'data-dismiss' => 'alert'}
|
22
|
+
.button.close{'data-dismiss' => 'alert'} ×
|
23
23
|
<%- error %>
|
24
24
|
|
25
25
|
%script#dynamic-widget-plotarea{type: 'text/template'}
|
@@ -31,6 +31,7 @@
|
|
31
31
|
%button#refresh-chart.btn.btn-mini
|
32
32
|
%i.icon-refresh
|
33
33
|
Refresh chart
|
34
|
+
.form-inline
|
34
35
|
Timespan:
|
35
36
|
%select#extend-timespan-val.btn-mini.span1
|
36
37
|
= partial "widgets/extend_options"
|
@@ -40,6 +41,18 @@
|
|
40
41
|
%button#reset-timespan.btn.btn-mini
|
41
42
|
%i.icon-arrow-right
|
42
43
|
Reset
|
44
|
+
.form-inline
|
45
|
+
%label
|
46
|
+
From:
|
47
|
+
%span#start-time
|
48
|
+
%input.datepicker
|
49
|
+
%label
|
50
|
+
Till:
|
51
|
+
%span#end-time
|
52
|
+
%input.datepicker
|
53
|
+
%button#set-interval.btn.btn-mini
|
54
|
+
%i.icon-time
|
55
|
+
Set interval
|
43
56
|
|
44
57
|
|
45
58
|
%script#dynamic-widget{type: 'text/template'}
|
@@ -5,27 +5,49 @@
|
|
5
5
|
%button#refresh.btn.btn-mini
|
6
6
|
%i.icon-refresh
|
7
7
|
Refresh
|
8
|
-
%
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
%
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
8
|
+
%a#configure-button.btn.btn-mini{href: "#configure", role: 'button', 'data-toggle' => 'modal'}
|
9
|
+
%i.icon-wrench
|
10
|
+
Configure...
|
11
|
+
.modal.hide.fade#configure
|
12
|
+
.modal-header
|
13
|
+
%button.close{type: 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} ×
|
14
|
+
%h3 Configure
|
15
|
+
.modal-body
|
16
|
+
%p
|
17
|
+
%label
|
18
|
+
Cutoff min:
|
19
|
+
%input.btn-mini#cutoff-min
|
20
|
+
%span.space
|
21
|
+
%label
|
22
|
+
Cutoff max:
|
23
|
+
%input.btn-mini#cutoff-max
|
24
|
+
%hr
|
25
|
+
%p
|
26
|
+
%label
|
27
|
+
Refresh:
|
28
|
+
%input.btn-mini#need-refresh{type: :checkbox, checked: :true}
|
29
|
+
%hr
|
30
|
+
%p
|
31
|
+
%label
|
32
|
+
Timespan:
|
33
|
+
%select#extend-timespan-val.btn-mini.span1
|
34
|
+
= partial "widgets/extend_options"
|
35
|
+
%button#extend-timespan.btn.btn-mini
|
36
|
+
%i.icon-arrow-left
|
37
|
+
Extend
|
38
|
+
%button#reset-timespan.btn.btn-mini
|
39
|
+
%i.icon-arrow-right
|
40
|
+
Reset
|
41
|
+
%hr
|
42
|
+
%p
|
43
|
+
%label
|
44
|
+
From:
|
45
|
+
%span#start-time
|
46
|
+
%input.datepicker
|
47
|
+
%label
|
48
|
+
Till:
|
49
|
+
%span#end-time
|
50
|
+
%input.datepicker
|
51
|
+
%button#set-interval.btn.btn-mini
|
52
|
+
%i.icon-time
|
53
|
+
Set interval
|
@@ -5,27 +5,50 @@
|
|
5
5
|
%button#refresh.btn.btn-mini
|
6
6
|
%i.icon-refresh
|
7
7
|
Refresh
|
8
|
-
%
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
%
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
8
|
+
%a#configure-button.btn.btn-mini{href: "#configure", role: 'button', 'data-toggle' => 'modal'}
|
9
|
+
%i.icon-wrench
|
10
|
+
Configure...
|
11
|
+
.modal.hide.fade#configure
|
12
|
+
.modal-header
|
13
|
+
%button.close{type: 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} ×
|
14
|
+
%h3 Configure
|
15
|
+
.modal-body
|
16
|
+
%p
|
17
|
+
%label
|
18
|
+
Cutoff min:
|
19
|
+
%input.btn-mini#cutoff-min
|
20
|
+
%span.space
|
21
|
+
%label
|
22
|
+
Cutoff max:
|
23
|
+
%input.btn-mini#cutoff-max
|
24
|
+
%hr
|
25
|
+
%p
|
26
|
+
%label
|
27
|
+
Refresh:
|
28
|
+
%input.btn-mini#need-refresh{type: :checkbox, checked: :true}
|
29
|
+
%hr
|
30
|
+
%p
|
31
|
+
%label
|
32
|
+
Timespan:
|
33
|
+
%select#extend-timespan-val.btn-mini.span1
|
34
|
+
= partial "widgets/extend_options"
|
35
|
+
%button#extend-timespan.btn.btn-mini
|
36
|
+
%i.icon-arrow-left
|
37
|
+
Extend
|
38
|
+
%button#reset-timespan.btn.btn-mini
|
39
|
+
%i.icon-arrow-right
|
40
|
+
Reset
|
41
|
+
%hr
|
42
|
+
%p
|
43
|
+
%label
|
44
|
+
From:
|
45
|
+
%span#start-time
|
46
|
+
%input.datepicker
|
47
|
+
%label
|
48
|
+
Till:
|
49
|
+
%span#end-time
|
50
|
+
%input.datepicker
|
51
|
+
%button#set-interval.btn.btn-mini
|
52
|
+
%i.icon-time
|
53
|
+
Set interval
|
31
54
|
|
@@ -5,19 +5,41 @@
|
|
5
5
|
%button#refresh.btn.btn-mini
|
6
6
|
%i.icon-refresh
|
7
7
|
Refresh
|
8
|
-
%
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
%
|
21
|
-
|
22
|
-
|
8
|
+
%a#configure-button.btn.btn-mini{href: "#configure", role: 'button', 'data-toggle' => 'modal'}
|
9
|
+
%i.icon-wrench
|
10
|
+
Configure...
|
11
|
+
.modal.hide.fade#configure
|
12
|
+
.modal-header
|
13
|
+
%button.close{type: 'button', 'data-dismiss' => 'modal', 'aria-hidden' => 'true'} ×
|
14
|
+
%h3 Configure
|
15
|
+
.modal-body
|
16
|
+
%p
|
17
|
+
%label
|
18
|
+
Refresh:
|
19
|
+
%input.btn-mini#need-refresh{type: :checkbox, checked: :true}
|
20
|
+
%hr
|
21
|
+
%p
|
22
|
+
%label
|
23
|
+
Timespan:
|
24
|
+
%select#extend-timespan-val.btn-mini.span1
|
25
|
+
= partial "widgets/extend_options"
|
26
|
+
%button#extend-timespan.btn.btn-mini
|
27
|
+
%i.icon-arrow-left
|
28
|
+
Extend
|
29
|
+
%button#reset-timespan.btn.btn-mini
|
30
|
+
%i.icon-arrow-right
|
31
|
+
Reset
|
32
|
+
%hr
|
33
|
+
%p
|
34
|
+
%label
|
35
|
+
From:
|
36
|
+
%span#start-time
|
37
|
+
%input.datepicker
|
38
|
+
%label
|
39
|
+
Till:
|
40
|
+
%span#end-time
|
41
|
+
%input.datepicker
|
42
|
+
%button#set-interval.btn.btn-mini
|
43
|
+
%i.icon-time
|
44
|
+
Set interval
|
23
45
|
|
@@ -15,10 +15,10 @@ module PulseMeter
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def data(options = {})
|
18
|
-
|
18
|
+
from, till = get_interval_borders(options)
|
19
19
|
super().merge({
|
20
20
|
values_title: values_label,
|
21
|
-
series: series_data(
|
21
|
+
series: series_data(from, till),
|
22
22
|
timespan: timespan,
|
23
23
|
interval: interval
|
24
24
|
})
|
@@ -26,11 +26,26 @@ module PulseMeter
|
|
26
26
|
|
27
27
|
protected
|
28
28
|
|
29
|
-
def
|
29
|
+
def get_interval_borders(options)
|
30
|
+
from = if options[:start_time] && (options[:start_time] > 0)
|
31
|
+
Time.at options[:start_time]
|
32
|
+
else
|
33
|
+
tspan = options[:timespan] || timespan
|
34
|
+
Time.now - tspan
|
35
|
+
end
|
36
|
+
|
37
|
+
till = if options[:end_time] && (options[:end_time] > 0)
|
38
|
+
Time.at options[:end_time]
|
39
|
+
else
|
40
|
+
Time.now
|
41
|
+
end
|
42
|
+
[from, till]
|
43
|
+
end
|
44
|
+
|
45
|
+
def series_data(from, till)
|
30
46
|
ensure_sensor_match!
|
31
|
-
now = Time.now
|
32
47
|
sensor_datas = sensors.map{ |s|
|
33
|
-
s.timeline_data(
|
48
|
+
s.timeline_data(from, till, show_last_point)
|
34
49
|
}
|
35
50
|
rows = []
|
36
51
|
titles = []
|
data/pulse-meter.gemspec
CHANGED
@@ -28,14 +28,18 @@ Gem::Specification.new do |gem|
|
|
28
28
|
gem.add_runtime_dependency('terminal-table')
|
29
29
|
gem.add_runtime_dependency('thor')
|
30
30
|
|
31
|
+
gem.add_development_dependency('coffee-script')
|
31
32
|
gem.add_development_dependency('foreman')
|
32
33
|
gem.add_development_dependency('hashie')
|
34
|
+
gem.add_development_dependency('listen')
|
33
35
|
gem.add_development_dependency('mock_redis')
|
34
36
|
gem.add_development_dependency('rack-test')
|
35
37
|
gem.add_development_dependency('rake')
|
38
|
+
gem.add_development_dependency('rb-fsevent')
|
36
39
|
gem.add_development_dependency('redcarpet')
|
37
40
|
gem.add_development_dependency('rspec')
|
38
41
|
gem.add_development_dependency('simplecov')
|
42
|
+
gem.add_development_dependency('sprockets')
|
39
43
|
gem.add_development_dependency('timecop')
|
40
44
|
gem.add_development_dependency('yard')
|
41
45
|
|
@@ -0,0 +1,252 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PulseMeter::Observer do
|
4
|
+
|
5
|
+
context "instance methods observation" do
|
6
|
+
|
7
|
+
class Dummy
|
8
|
+
attr_reader :count
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@count = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def incr(value = 1, &proc)
|
15
|
+
Timecop.travel(Time.now + 1)
|
16
|
+
@count += value
|
17
|
+
@count += proc.call if proc
|
18
|
+
@count
|
19
|
+
end
|
20
|
+
|
21
|
+
def error
|
22
|
+
raise RuntimeError
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let!(:dummy) {Dummy.new}
|
27
|
+
let!(:sensor) {PulseMeter::Sensor::Counter.new(:foo)}
|
28
|
+
before do
|
29
|
+
[:incr, :error].each {|m| described_class.unobserve_method(Dummy, m)}
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_observer(method = :incr, increment = 1)
|
33
|
+
described_class.observe_method(Dummy, method, sensor) do |*args|
|
34
|
+
event(increment)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def remove_observer(method = :incr)
|
39
|
+
described_class.unobserve_method(Dummy, method)
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".observe_method" do
|
43
|
+
it "executes block in context of sensor each time specified method of given class called" do
|
44
|
+
create_observer
|
45
|
+
5.times {dummy.incr}
|
46
|
+
sensor.value.should == 5
|
47
|
+
end
|
48
|
+
|
49
|
+
it "passes arguments to observed method" do
|
50
|
+
create_observer
|
51
|
+
5.times {dummy.incr(10)}
|
52
|
+
dummy.count.should == 50
|
53
|
+
end
|
54
|
+
|
55
|
+
it "passes methods' params to block" do
|
56
|
+
described_class.observe_method(Dummy, :incr, sensor) do |time, cnt|
|
57
|
+
event(cnt)
|
58
|
+
end
|
59
|
+
|
60
|
+
5.times {dummy.incr(10)}
|
61
|
+
sensor.value.should == 50
|
62
|
+
end
|
63
|
+
|
64
|
+
it "passes execution time in milliseconds to block" do
|
65
|
+
Timecop.freeze do
|
66
|
+
described_class.observe_method(Dummy, :incr, sensor) do |time, cnt|
|
67
|
+
event(time)
|
68
|
+
end
|
69
|
+
|
70
|
+
dummy.incr
|
71
|
+
sensor.value.should >= 1000
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "does not break observed method even is observer raises error" do
|
76
|
+
described_class.observe_method(Dummy, :incr, sensor) do |*args|
|
77
|
+
raise RuntimeError
|
78
|
+
end
|
79
|
+
|
80
|
+
lambda {dummy.incr}.should_not raise_error
|
81
|
+
dummy.count.should == 1
|
82
|
+
end
|
83
|
+
|
84
|
+
it "uses first observer in case of double observation" do
|
85
|
+
create_observer(:incr, 1)
|
86
|
+
create_observer(:incr, 2)
|
87
|
+
5.times {dummy.incr}
|
88
|
+
sensor.value.should == 5
|
89
|
+
end
|
90
|
+
|
91
|
+
it "keeps observed methods' errors" do
|
92
|
+
create_observer(:error)
|
93
|
+
lambda {dummy.error}.should raise_error(RuntimeError)
|
94
|
+
sensor.value.should == 1
|
95
|
+
end
|
96
|
+
|
97
|
+
it "makes observed method return its value" do
|
98
|
+
create_observer
|
99
|
+
dummy.incr.should == 1
|
100
|
+
end
|
101
|
+
|
102
|
+
it "allows to pass blocks to observed method" do
|
103
|
+
create_observer
|
104
|
+
dummy.incr do
|
105
|
+
2
|
106
|
+
end
|
107
|
+
dummy.count.should == 3
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe ".unobserve_method" do
|
112
|
+
it "does nothing unless method is observed" do
|
113
|
+
lambda {remove_observer}.should_not raise_error
|
114
|
+
end
|
115
|
+
|
116
|
+
it "removes observation from observed method" do
|
117
|
+
create_observer
|
118
|
+
dummy.incr
|
119
|
+
remove_observer
|
120
|
+
dummy.incr
|
121
|
+
sensor.value.should == 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
context "class methods observation" do
|
127
|
+
|
128
|
+
class Dummy
|
129
|
+
@@count = 0
|
130
|
+
class << self
|
131
|
+
def count
|
132
|
+
@@count
|
133
|
+
end
|
134
|
+
|
135
|
+
def reset
|
136
|
+
@@count = 0
|
137
|
+
end
|
138
|
+
|
139
|
+
def incr(value = 1, &proc)
|
140
|
+
Timecop.travel(Time.now + 1)
|
141
|
+
@@count += value
|
142
|
+
@@count += proc.call if proc
|
143
|
+
@@count
|
144
|
+
end
|
145
|
+
|
146
|
+
def error
|
147
|
+
raise RuntimeError
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
let!(:dummy) {Dummy}
|
153
|
+
let!(:sensor) {PulseMeter::Sensor::Counter.new(:foo)}
|
154
|
+
before do
|
155
|
+
dummy.reset
|
156
|
+
[:incr, :error].each {|m| described_class.unobserve_class_method(Dummy, m)}
|
157
|
+
end
|
158
|
+
|
159
|
+
def create_observer(method = :incr, increment = 1)
|
160
|
+
described_class.observe_class_method(Dummy, method, sensor) do |*args|
|
161
|
+
event(increment)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def remove_observer(method = :incr)
|
166
|
+
described_class.unobserve_class_method(Dummy, method)
|
167
|
+
end
|
168
|
+
|
169
|
+
describe ".observe_class_method" do
|
170
|
+
it "executes block in context of sensor each time specified method of given class called" do
|
171
|
+
create_observer
|
172
|
+
5.times {dummy.incr}
|
173
|
+
sensor.value.should == 5
|
174
|
+
end
|
175
|
+
|
176
|
+
it "passes arguments to observed method" do
|
177
|
+
create_observer
|
178
|
+
5.times {dummy.incr(10)}
|
179
|
+
dummy.count.should == 50
|
180
|
+
end
|
181
|
+
|
182
|
+
it "passes methods' params to block" do
|
183
|
+
described_class.observe_class_method(Dummy, :incr, sensor) do |time, cnt|
|
184
|
+
event(cnt)
|
185
|
+
end
|
186
|
+
|
187
|
+
5.times {dummy.incr(10)}
|
188
|
+
sensor.value.should == 50
|
189
|
+
end
|
190
|
+
|
191
|
+
it "passes execution time in milliseconds to block" do
|
192
|
+
Timecop.freeze do
|
193
|
+
described_class.observe_class_method(Dummy, :incr, sensor) do |time, cnt|
|
194
|
+
event(time)
|
195
|
+
end
|
196
|
+
|
197
|
+
dummy.incr
|
198
|
+
sensor.value.should == 1000
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it "does not break observed method even is observer raises error" do
|
203
|
+
described_class.observe_class_method(Dummy, :incr, sensor) do |*args|
|
204
|
+
raise RuntimeError
|
205
|
+
end
|
206
|
+
|
207
|
+
lambda {dummy.incr}.should_not raise_error
|
208
|
+
dummy.count.should == 1
|
209
|
+
end
|
210
|
+
|
211
|
+
it "uses first observer in case of double observation" do
|
212
|
+
create_observer(:incr, 1)
|
213
|
+
create_observer(:incr, 2)
|
214
|
+
5.times {dummy.incr}
|
215
|
+
sensor.value.should == 5
|
216
|
+
end
|
217
|
+
|
218
|
+
it "keeps observed methods' errors" do
|
219
|
+
create_observer(:error)
|
220
|
+
lambda {dummy.error}.should raise_error(RuntimeError)
|
221
|
+
sensor.value.should == 1
|
222
|
+
end
|
223
|
+
|
224
|
+
it "makes observed method return its value" do
|
225
|
+
create_observer
|
226
|
+
dummy.incr.should == 1
|
227
|
+
end
|
228
|
+
|
229
|
+
it "allows to pass blocks to observed method" do
|
230
|
+
create_observer
|
231
|
+
dummy.incr do
|
232
|
+
2
|
233
|
+
end
|
234
|
+
dummy.count.should == 3
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe ".unobserve_class_method" do
|
239
|
+
it "does nothing unless method is observed" do
|
240
|
+
lambda {remove_observer}.should_not raise_error
|
241
|
+
end
|
242
|
+
|
243
|
+
it "removes observation from observed method" do
|
244
|
+
create_observer
|
245
|
+
dummy.incr
|
246
|
+
remove_observer
|
247
|
+
dummy.incr
|
248
|
+
sensor.value.should == 1
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|