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.
Files changed (79) hide show
  1. data/.rbenv-version +1 -1
  2. data/README.md +32 -16
  3. data/Rakefile +29 -10
  4. data/examples/basic.ru +1 -1
  5. data/examples/full/server.ru +1 -1
  6. data/examples/minimal/server.ru +1 -1
  7. data/lib/pulse-meter.rb +1 -0
  8. data/lib/pulse-meter/observer.rb +117 -0
  9. data/lib/pulse-meter/sensor.rb +1 -0
  10. data/lib/pulse-meter/sensor/timeline.rb +3 -2
  11. data/lib/pulse-meter/sensor/timelined/multi_percentile.rb +43 -0
  12. data/lib/pulse-meter/version.rb +1 -1
  13. data/lib/pulse-meter/visualize/app.rb +28 -12
  14. data/lib/pulse-meter/visualize/coffee/application.coffee +40 -0
  15. data/lib/pulse-meter/visualize/coffee/collections/page_info_list.coffee +17 -0
  16. data/lib/pulse-meter/visualize/coffee/collections/sensor_info_list.coffee +4 -0
  17. data/lib/pulse-meter/visualize/coffee/collections/widget_list.coffee +14 -0
  18. data/lib/pulse-meter/visualize/coffee/extensions.coffee +26 -0
  19. data/lib/pulse-meter/visualize/coffee/models/dinamic_widget.coffee +34 -0
  20. data/lib/pulse-meter/visualize/coffee/models/page_info.coffee +2 -0
  21. data/lib/pulse-meter/visualize/coffee/models/sensor_info.coffee +2 -0
  22. data/lib/pulse-meter/visualize/coffee/models/widget.coffee +54 -0
  23. data/lib/pulse-meter/visualize/coffee/presenters/area.coffee +2 -0
  24. data/lib/pulse-meter/visualize/coffee/presenters/gauge.coffee +11 -0
  25. data/lib/pulse-meter/visualize/coffee/presenters/line.coffee +2 -0
  26. data/lib/pulse-meter/visualize/coffee/presenters/pie.coffee +20 -0
  27. data/lib/pulse-meter/visualize/coffee/presenters/series.coffee +44 -0
  28. data/lib/pulse-meter/visualize/coffee/presenters/table.coffee +10 -0
  29. data/lib/pulse-meter/visualize/coffee/presenters/timeline.coffee +13 -0
  30. data/lib/pulse-meter/visualize/coffee/presenters/widget.coffee +65 -0
  31. data/lib/pulse-meter/visualize/coffee/router.coffee +21 -0
  32. data/lib/pulse-meter/visualize/coffee/views/dynamic_chart.coffee +91 -0
  33. data/lib/pulse-meter/visualize/coffee/views/dynamic_widget.coffee +58 -0
  34. data/lib/pulse-meter/visualize/coffee/views/page_title.coffee +17 -0
  35. data/lib/pulse-meter/visualize/coffee/views/page_titles.coffee +15 -0
  36. data/lib/pulse-meter/visualize/coffee/views/sensor_info_list.coffee +19 -0
  37. data/lib/pulse-meter/visualize/coffee/views/widget.coffee +99 -0
  38. data/lib/pulse-meter/visualize/coffee/views/widget_chart.coffee +13 -0
  39. data/lib/pulse-meter/visualize/coffee/views/widget_list.coffee +15 -0
  40. data/lib/pulse-meter/visualize/layout.rb +4 -4
  41. data/lib/pulse-meter/visualize/public/css/application.css +13 -4
  42. data/lib/pulse-meter/visualize/public/css/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  43. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  44. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  45. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  46. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  47. data/lib/pulse-meter/visualize/public/css/images/ui-bg_glass_75_ffffff_1x400.png +0 -0
  48. data/lib/pulse-meter/visualize/public/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  49. data/lib/pulse-meter/visualize/public/css/images/ui-bg_inset-soft_95_fef1ec_1x100.png +0 -0
  50. data/lib/pulse-meter/visualize/public/css/images/ui-icons_222222_256x240.png +0 -0
  51. data/lib/pulse-meter/visualize/public/css/images/ui-icons_2e83ff_256x240.png +0 -0
  52. data/lib/pulse-meter/visualize/public/css/images/ui-icons_454545_256x240.png +0 -0
  53. data/lib/pulse-meter/visualize/public/css/images/ui-icons_888888_256x240.png +0 -0
  54. data/lib/pulse-meter/visualize/public/css/images/ui-icons_cd0a0a_256x240.png +0 -0
  55. data/lib/pulse-meter/visualize/public/css/images/ui-icons_f6cf3b_256x240.png +0 -0
  56. data/lib/pulse-meter/visualize/public/css/jquery-ui-1.8.16.bootstrap.css +1320 -0
  57. data/lib/pulse-meter/visualize/public/js/application.js +900 -691
  58. data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.16.bootstrap.min.js +791 -0
  59. data/lib/pulse-meter/visualize/public/js/jquery-ui-1.8.23.custom.min.js +21 -0
  60. data/lib/pulse-meter/visualize/public/js/jquery-ui-timepicker-addon.js +1687 -0
  61. data/lib/pulse-meter/visualize/sensor.rb +2 -2
  62. data/lib/pulse-meter/visualize/views/main.haml +2 -3
  63. data/lib/pulse-meter/visualize/views/sensors.haml +14 -1
  64. data/lib/pulse-meter/visualize/views/widgets/area.haml +46 -24
  65. data/lib/pulse-meter/visualize/views/widgets/line.haml +46 -23
  66. data/lib/pulse-meter/visualize/views/widgets/table.haml +37 -15
  67. data/lib/pulse-meter/visualize/widgets/timeline.rb +20 -5
  68. data/pulse-meter.gemspec +4 -0
  69. data/spec/pulse_meter/observer_spec.rb +252 -0
  70. data/spec/pulse_meter/sensor/timelined/multi_percentile_spec.rb +21 -0
  71. data/spec/pulse_meter/visualize/sensor_spec.rb +5 -5
  72. data/spec/pulse_meter/visualize/widgets/area_spec.rb +1 -74
  73. data/spec/pulse_meter/visualize/widgets/line_spec.rb +1 -73
  74. data/spec/pulse_meter/visualize/widgets/table_spec.rb +1 -73
  75. data/spec/shared_examples/timeline_sensor.rb +10 -0
  76. data/spec/shared_examples/widget.rb +97 -0
  77. data/spec/spec_helper.rb +1 -0
  78. metadata +120 -5
  79. 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(now, time_span, need_incomplete = false)
26
+ def timeline_data(from, till, need_incomplete = false)
27
27
  sensor = real_sensor
28
- timeline_data = sensor.timeline_within(now - time_span, now)
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
- %span.space
9
- %label
10
- Cutoff min:
11
- %input.btn-mini#cutoff-min
12
- %span.space
13
- %label
14
- Cutoff max:
15
- %input.btn-mini#cutoff-max
16
- %span.space
17
- %label
18
- Refresh:
19
- %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
20
- %label
21
- Timespan:
22
- %select#extend-timespan-val.btn-mini.span1
23
- = partial "widgets/extend_options"
24
- %button#extend-timespan.btn.btn-mini
25
- %i.icon-arrow-left
26
- Extend
27
- %button#reset-timespan.btn.btn-mini
28
- %i.icon-arrow-right
29
- Reset
30
- %hr
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'} &times;
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
- %span.space
9
- %label
10
- Cutoff min:
11
- %input.btn-mini#cutoff-min
12
- %span.space
13
- %label
14
- Cutoff max:
15
- %input.btn-mini#cutoff-max
16
- %span.space
17
- %label
18
- Refresh:
19
- %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
20
- %label
21
- Timespan:
22
- %select#extend-timespan-val.btn-mini.span1
23
- = partial "widgets/extend_options"
24
- %button#extend-timespan.btn.btn-mini
25
- %i.icon-arrow-left
26
- Extend
27
- %button#reset-timespan.btn.btn-mini
28
- %i.icon-arrow-right
29
- Reset
30
- %hr
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'} &times;
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
- %span.space
9
- %label
10
- Refresh:
11
- %input.btn-mini#need-refresh{type: :checkbox, checked: :true}
12
- %label
13
- Timespan:
14
- %select#extend-timespan-val.btn-mini.span1
15
- = partial "widgets/extend_options"
16
- %button#extend-timespan.btn.btn-mini
17
- %i.icon-arrow-down
18
- Extend
19
- %button#reset-timespan.btn.btn-mini
20
- %i.icon-arrow-up
21
- Reset
22
- %hr
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'} &times;
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
- real_timespan = options[:timespan] || timespan
18
+ from, till = get_interval_borders(options)
19
19
  super().merge({
20
20
  values_title: values_label,
21
- series: series_data(real_timespan),
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 series_data(tspan)
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(now, tspan, show_last_point)
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