active_metric 2.5.2

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 (45) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +5 -0
  3. data/Rakefile +37 -0
  4. data/lib/active_metric.rb +38 -0
  5. data/lib/active_metric/axis.rb +11 -0
  6. data/lib/active_metric/behavior/calculates_derivative.rb +18 -0
  7. data/lib/active_metric/behavior/graph_calculation.rb +78 -0
  8. data/lib/active_metric/calculators/reservoir.rb +81 -0
  9. data/lib/active_metric/calculators/standard_deviator.rb +39 -0
  10. data/lib/active_metric/config/initializers/inflections.rb +4 -0
  11. data/lib/active_metric/graph_view_model.rb +60 -0
  12. data/lib/active_metric/measurement.rb +13 -0
  13. data/lib/active_metric/point_series_data.rb +17 -0
  14. data/lib/active_metric/report.rb +57 -0
  15. data/lib/active_metric/report_view_model.rb +106 -0
  16. data/lib/active_metric/sample.rb +148 -0
  17. data/lib/active_metric/series_data.rb +26 -0
  18. data/lib/active_metric/stat.rb +72 -0
  19. data/lib/active_metric/stat_definition.rb +20 -0
  20. data/lib/active_metric/statistics/defaults.rb +157 -0
  21. data/lib/active_metric/statistics/standard_deviation.rb +43 -0
  22. data/lib/active_metric/subject.rb +117 -0
  23. data/lib/active_metric/version.rb +3 -0
  24. data/test/active_metric_test.rb +30 -0
  25. data/test/axis_test.rb +22 -0
  26. data/test/behavior_tests/calculates_derivative_test.rb +35 -0
  27. data/test/behavior_tests/graph_calculation_test.rb +68 -0
  28. data/test/config/mongoid.yml +13 -0
  29. data/test/dummy/db/test.sqlite3 +0 -0
  30. data/test/dummy/log/test.log +18597 -0
  31. data/test/graph_view_model_test.rb +92 -0
  32. data/test/integration_test.rb +149 -0
  33. data/test/measurement_test.rb +45 -0
  34. data/test/mongoid_test.rb +24 -0
  35. data/test/point_series_data_test.rb +27 -0
  36. data/test/report_test.rb +73 -0
  37. data/test/report_view_model_test.rb +94 -0
  38. data/test/reservoir_test.rb +67 -0
  39. data/test/sample_test.rb +142 -0
  40. data/test/series_data_test.rb +20 -0
  41. data/test/standard_deviator_test.rb +45 -0
  42. data/test/stat_test.rb +222 -0
  43. data/test/subject_test.rb +22 -0
  44. data/test/test_helper.rb +76 -0
  45. metadata +123 -0
@@ -0,0 +1,67 @@
1
+ require_relative "test_helper"
2
+
3
+ module ActiveMetric
4
+ class ReservoirTest < ActiveSupport::TestCase
5
+
6
+ class Measurement
7
+ attr_accessor :value
8
+ end
9
+
10
+ setup do
11
+ @reservoir = Reservoir.new(10)
12
+ end
13
+
14
+ test "can initialize reservoir with type and size" do
15
+ assert_equal @reservoir.size, 10
16
+ end
17
+
18
+ test "can fill reservoir" do
19
+ @reservoir.fill create_measurement(1)
20
+ end
21
+
22
+ test "can fill reservoir and calculate percentile" do
23
+ (1..10).reverse_each do |value|
24
+ @reservoir.fill create_measurement(value)
25
+ end
26
+ assert_equal 9, @reservoir.calculate_percentile(0.8, :value)
27
+ assert_equal 10, @reservoir.calculate_percentile(0.98, :value)
28
+ end
29
+
30
+ test "calculates based on moving window" do
31
+ (1..20).reverse_each do |value|
32
+ @reservoir.fill create_measurement(value)
33
+ end
34
+
35
+ assert_equal 9, @reservoir.calculate_percentile(0.8, :value)
36
+ assert_equal 10, @reservoir.calculate_percentile(0.98, :value)
37
+ end
38
+
39
+ test "can calculate standard deviation for window" do
40
+ reverse_fill(1..20)
41
+ assert_close_to 2.87, @reservoir.calculate_standard_deviation(:value)
42
+ reverse_fill(95..100)
43
+ assert_close_to 46.56, @reservoir.calculate_standard_deviation(:value)
44
+ reverse_fill(90..95)
45
+ assert_close_to 2.47, @reservoir.calculate_standard_deviation(:value)
46
+ end
47
+
48
+ private
49
+
50
+ def reverse_fill(range)
51
+ range.reverse_each do |value|
52
+ @reservoir.fill create_measurement(value)
53
+ end
54
+ end
55
+
56
+ def shuffle(array)
57
+ array.size.downto(1) { |n| array.push array.delete_at(rand(n)) }
58
+ end
59
+
60
+ def create_measurement(value)
61
+ measurement = Measurement.new
62
+ measurement.value = value
63
+ measurement
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,142 @@
1
+ require_relative "test_helper"
2
+
3
+ module ActiveMetric
4
+ class SampleTest < ActiveSupport::TestCase
5
+
6
+ test "should have the correct stats by name" do
7
+ subject = TestSubject.new
8
+ sample = TestSample.new(:samplable => subject)
9
+ assert_equal 12, sample.stats.size
10
+ assert_kind_of Min, sample.min_value
11
+ assert_kind_of Mean, sample.mean_value
12
+ assert_kind_of Max, sample.max_value
13
+ assert_kind_of Eightieth, sample.eightieth_value
14
+ assert_kind_of NinetyEighth, sample.ninety_eighth_value
15
+ assert_kind_of StandardDeviation, sample.standard_deviation_value
16
+ assert_kind_of Delta, sample.delta_value
17
+ assert_kind_of Bucket, sample.bucket_value
18
+ assert_kind_of Speed, sample.speed_value
19
+ assert_kind_of PercentFalse, sample.percent_false_value
20
+ assert_kind_of TestCount, sample.test_count
21
+ assert_kind_of TestResponseCodes, sample.test_response_codes
22
+ end
23
+
24
+ test "samples spawned from samples contain seed measurement" do
25
+ subject = TestSubject.new
26
+
27
+ num_measurements = 5
28
+
29
+ sample = TestSample.new(:interval => 6, :samplable => subject)
30
+
31
+ num_measurements.times do |time|
32
+ sample.calculate TestMeasurement.new :value => 1, :timestamp => time
33
+ end
34
+
35
+ new_sample = sample.new_sample
36
+
37
+ assert_not_equal sample, new_sample
38
+ assert_equal 4, new_sample.seed_measurement.timestamp
39
+ assert_equal sample.latest_measurement, new_sample.seed_measurement
40
+ end
41
+
42
+
43
+ test "should have correct timestamp" do
44
+ measurements = [TestMeasurement.new(:value => 10, :timestamp => 1),
45
+ TestMeasurement.new(:value => 12, :timestamp => 2),
46
+ TestMeasurement.new(:value => 11, :timestamp => 9)]
47
+
48
+ subject = TestSubject.create
49
+ sample = TestSample.new(:samplable => subject)
50
+ measurements.each do |measurement|
51
+ sample.calculate(measurement)
52
+ end
53
+ assert_equal (4), sample.timestamp
54
+ end
55
+
56
+ test "should call calculate on all stats" do
57
+ subject = TestSubject.create
58
+ sample = TestSample.new(:samplable => subject)
59
+ 5.times do |value|
60
+ measurement = TestMeasurement.new :value => value, :timestamp => 1
61
+ sample.stats.each do |stat|
62
+ stat.expects(:calculate).with(measurement)
63
+ end
64
+ sample.calculate measurement
65
+ end
66
+
67
+ sample.stats.each do |stat|
68
+ stat.expects(:complete)
69
+ end
70
+ sample.complete
71
+ end
72
+
73
+ test "should return raw stat if sample does not have that stat" do
74
+ sample = TestSample.new
75
+ stat = sample.this_stat_does_not_exist
76
+ assert_equal 0, stat.value
77
+ end
78
+
79
+ test "should return stat if sample has it" do
80
+ sample = TestSample.new
81
+ stat = sample.min_value
82
+ assert stat.kind_of? Min
83
+ end
84
+
85
+ test "is summary" do
86
+ subject = TestSubject.create
87
+ summary = subject.summary
88
+ assert summary.is_summary?
89
+ end
90
+
91
+ test "summary only should set start time after first measurement" do
92
+ subject = TestSubject.create
93
+ summary = subject.summary
94
+ assert_nil summary.start_time
95
+
96
+
97
+ summary.calculate(TestMeasurement.new :value => 1, :timestamp => 1)
98
+ assert_equal 1, summary.start_time
99
+ end
100
+
101
+ test "duration from previous sample defaults to current sample duration if no previous sample" do
102
+ nil_seed_measurement = nil
103
+ sample = TestSample.new({},nil_seed_measurement)
104
+ sample.expects(:duration_in_seconds)
105
+ sample.duration_from_previous_sample_in_seconds
106
+ end
107
+
108
+ test "duration from previous sample returns correct duration" do
109
+ seed_measurement = mock
110
+
111
+ seed_measurement.expects(:timestamp).returns(100)
112
+
113
+ sample = TestSample.new({},{}, seed_measurement)
114
+ sample.expects(:end_time).returns(200)
115
+
116
+ assert_equal 100, sample.duration_from_previous_sample_in_seconds
117
+ end
118
+
119
+ test "passing in just a hash to create sets seed measurement to nil" do
120
+ subject = TestSubject.new
121
+
122
+ sample = TestSample.create( {:samplable => subject, :foo => "bar"} )
123
+ assert_nil sample.seed_measurement
124
+
125
+ end
126
+
127
+ test "stat definitions have proper axis" do
128
+ @stat_definitions = TestSample.stats_defined
129
+ assert_contains_axis(:min_value,0)
130
+ assert_contains_axis(:mean_value,0)
131
+ assert_contains_axis(:max_value,0)
132
+ assert_contains_axis(:standard_deviation_value,1)
133
+ assert_contains_axis(:test_count,1)
134
+ assert_contains_axis(:test_response_codes, -1)
135
+ end
136
+
137
+ def assert_contains_axis(stat,axis)
138
+ assert_equal axis, @stat_definitions.select {|sd| sd.access_name == stat}.first.options[:axis]
139
+ end
140
+
141
+ end
142
+ end
@@ -0,0 +1,20 @@
1
+ require_relative "test_helper"
2
+
3
+ module ActiveMetric
4
+ class SeriesDataTest < ActiveSupport::TestCase
5
+
6
+ test "can instantiate from stat definition" do
7
+ access_name = "name_of_stat"
8
+ options = {axis: 2, visible: false}
9
+ stat_definition = StatDefinition.new(:property,Min,access_name,options)
10
+ series = SeriesData.from_stat_definition(stat_definition)
11
+
12
+ assert_equal access_name, series.label
13
+ assert_equal 2, series.y_axis
14
+ assert_equal 0, series.x_axis
15
+ assert_equal false, series.visible
16
+ end
17
+
18
+
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ require_relative "test_helper"
2
+ module ActiveMetric
3
+ class StandardDeviatorTest < ActiveSupport::TestCase
4
+
5
+ test "can calculate standard deviation" do
6
+ sd = StandardDeviator.new(:value)
7
+ test_stat(sd, 100.times)
8
+ assert_close_to 28.87, sd.standard_deviation
9
+ end
10
+
11
+ test "memoize standard deviation with a single measurement" do
12
+ sd = StandardDeviator.new(:value)
13
+ sd.expects(:calculate_standard_deviation).returns(5)
14
+ test_stat(sd, 1.times)
15
+ assert_equal 5, sd.standard_deviation
16
+ assert_equal 5, sd.standard_deviation
17
+ end
18
+
19
+ test "un-memoizes standard deviation with new measurement" do
20
+ sd = StandardDeviator.new(:value)
21
+ sd.expects(:calculate_standard_deviation).twice.returns(5,2)
22
+ test_stat(sd, 1.times)
23
+ assert_equal 5, sd.standard_deviation
24
+ assert_equal 5, sd.standard_deviation
25
+ test_stat(sd, 1.times)
26
+ assert_equal 2, sd.standard_deviation
27
+ end
28
+
29
+ test "can calculate standard deviation with no calculations " do
30
+ sd = StandardDeviator.new(:value)
31
+ assert_equal 0, sd.standard_deviation
32
+ end
33
+
34
+ private
35
+
36
+ def test_stat(stat, values)
37
+ values.each do |value|
38
+ stat.calculate TestMeasurement.new(:value => value)
39
+ end
40
+ end
41
+
42
+
43
+ end
44
+
45
+ end
data/test/stat_test.rb ADDED
@@ -0,0 +1,222 @@
1
+ require_relative 'test_helper'
2
+
3
+ module ActiveMetric
4
+ class StatTest < ActiveSupport::TestCase
5
+
6
+ setup do
7
+ @subject = TestSubject.create
8
+ @sample = TestSample.create({:samplable => @subject})
9
+ @stat = TestStat.new :property, :calculable => @sample
10
+ end
11
+
12
+ test "create a new stat" do
13
+ assert @stat
14
+ end
15
+
16
+ test "stat is a mongoid document" do
17
+ assert @stat.kind_of?(Mongoid::Document)
18
+ end
19
+
20
+ test "calculate raises an error if not implemented" do
21
+ assert_raise CannotInstantiateBaseStat do
22
+ @stat.calculate(mock)
23
+ end
24
+ end
25
+
26
+ test "has a dynamically defined name" do
27
+ assert_equal :test_stat_property, @stat.access_name
28
+ end
29
+
30
+ test "sets name for custom stat" do
31
+ proc = Proc.new { |m| self.value = m.value }
32
+ stat = Stat.create_custom_stat(:property, Integer, {}, proc).new(:property)
33
+ assert_equal :property, stat.access_name
34
+ end
35
+
36
+ test "custom stat uses block to calculate" do
37
+ proc = Proc.new { |m| self.value = m.value }
38
+ stat = Stat.create_custom_stat(:property, Integer, {}, proc).new(:property)
39
+ measurement = mock(:value => 10)
40
+ stat.calculate(measurement)
41
+ assert_equal 10, stat.value
42
+ end
43
+
44
+ test "can calculate min" do
45
+ stat = Min.new(:value, :calculable => @sample)
46
+ test_stat(stat, 10.times)
47
+ assert_equal 0, stat.value
48
+ end
49
+
50
+ test "can calculate max" do
51
+ stat = Max.new(:value, :calculable => @sample)
52
+ test_stat(stat, 10.times)
53
+ assert_equal 9, stat.value
54
+ end
55
+
56
+ test "can calculate mean" do
57
+ stat = Mean.new(:value, :calculable => @sample)
58
+ test_stat(stat, 10.times)
59
+ assert_equal 4.5, stat.value
60
+ end
61
+
62
+ test "can calculate derivative without seed_measurement" do
63
+ stat = Derivative.new(:value, :calculable => @sample)
64
+ @sample.stubs(:seed_measurement).returns(nil)
65
+
66
+ @sample.expects(:duration_from_previous_sample_in_seconds).times(2).returns(0, 1)
67
+ test_stat(stat, [1, 2])
68
+ slope_of_one = 1.0
69
+
70
+ assert_equal slope_of_one, stat.value
71
+ end
72
+
73
+ test "can calculate derivative with seed_measurement " do
74
+ stat = Derivative.new(:value, :calculable => @sample)
75
+
76
+ @sample.stubs(:seed_measurement).returns(test_seed_measurement)
77
+ @sample.expects(:duration_from_previous_sample_in_seconds).times(2).returns(1, 2)
78
+ test_stat(stat, [2, 3])
79
+
80
+ slope_of_one = 1.0
81
+
82
+ assert_equal slope_of_one, stat.value
83
+ end
84
+
85
+ test "can calculate last derivative without seed_measurement" do
86
+ stat = LastDerivative.new(:value, :calculable => @sample)
87
+ @sample.stubs(:seed_measurement).returns(nil)
88
+
89
+ test_stat(stat, [1])
90
+ no_derivative = 0
91
+
92
+ assert_equal no_derivative, stat.value
93
+ end
94
+
95
+ test "can calculate last derivative with seed measurement" do
96
+ stat = LastDerivative.new(:value, :calculable => @sample)
97
+
98
+ @sample.stubs(:seed_measurement).returns(test_seed_measurement)
99
+ test_stat(stat, [2, 4], [2, 3])
100
+
101
+ slope_of_two = 2.0
102
+
103
+ assert_equal slope_of_two, stat.value
104
+ end
105
+
106
+ test "can calculate speed" do
107
+ stat = Speed.new(:value, :calculable => @sample)
108
+
109
+ @sample.stubs(:seed_measurement).returns(test_seed_measurement)
110
+ @sample.expects(:duration_from_previous_sample_in_seconds).times(4).returns(1, 2, 3, 4)
111
+
112
+ test_stat(stat, [2, 3, 4, 5])
113
+
114
+ speed_of_one_per_second = 1.0
115
+ assert_equal speed_of_one_per_second, stat.value
116
+
117
+ end
118
+
119
+ test "can calculate delta without seed measurement" do
120
+ stat = Delta.new(:value, :calculable => @sample)
121
+ @sample.stubs(:seed_measurement).returns(nil)
122
+
123
+ test_stat(stat, [1, 5, 10])
124
+ assert_equal 9, stat.value
125
+ end
126
+
127
+ test "can calculate delta with seed measurement" do
128
+ stat = Delta.new(:value, :calculable => @sample)
129
+ @sample.stubs(:seed_measurement).returns(test_seed_measurement)
130
+
131
+ test_stat(stat, [5, 10])
132
+ assert_equal 9, stat.value
133
+ end
134
+
135
+ test "can calculate sum" do
136
+ stat = Sum.new(:value, :calculable => @sample)
137
+ test_stat(stat, 10.times)
138
+ assert_equal 45, stat.value
139
+ end
140
+
141
+ test "can calculate last" do
142
+ stat = Last.new(:value, :calculable => @sample)
143
+ test_stat(stat, 10.times)
144
+ assert_equal 9, stat.value
145
+ end
146
+
147
+ test "can calculate count" do
148
+ stat = Count.new(:value, :calculable => @sample)
149
+ test_stat(stat, 10.times)
150
+ assert_equal 10, stat.value
151
+ end
152
+
153
+ test "can calculate true count" do
154
+ stat = TrueCount.new(:value, :calculabe => @sample)
155
+ values = [true, true, false]
156
+ test_stat(stat, values)
157
+ assert_equal 2, stat.value
158
+ end
159
+
160
+ test "can calculate false count" do
161
+ stat = FalseCount.new(:value, :calculabe => @sample)
162
+ values = [true, true, false]
163
+ test_stat(stat, values)
164
+ assert_equal 1, stat.value
165
+ end
166
+
167
+ test "can bucket values" do
168
+ stat = Bucket.new(:value, :calculabe => @sample)
169
+ values = [200, 404, 500, 502, 200, 500, 504.2]
170
+ test_stat(stat, values)
171
+ expected_bucket = {
172
+ "200" => 2,
173
+ "404" => 1,
174
+ "500" => 2,
175
+ "502" => 1,
176
+ "504_2" => 1
177
+ }
178
+ assert_equal expected_bucket, stat.value
179
+ end
180
+
181
+ test "can calculate false percentage" do
182
+ stat = PercentFalse.new(:value, :calculabe => @sample)
183
+ values = [true, true, false, true]
184
+ test_stat(stat, values)
185
+ assert_equal 25, stat.value
186
+ end
187
+
188
+
189
+ #this test is here for the user, not for automated tests
190
+ #test "random distributions are good too" do
191
+ # stat = StandardDeviation.new(:value, :calculable => @sample)
192
+ # 100.times do
193
+ # stat.calculate TestMeasurement.create(:value => rand(100))
194
+ # end
195
+ # stat.complete
196
+ # assert_close_to 28.87, stat.value
197
+ #end
198
+
199
+ test "has subject" do
200
+ subject = TestSubject.create
201
+ sample = TestSample.create(:samplable => subject)
202
+ assert_equal subject, sample.stats.first.subject
203
+ end
204
+
205
+ private
206
+
207
+ def test_stat(stat, values, timestamps = values.to_a)
208
+ values.each_with_index do |value, index|
209
+ stat.calculate TestMeasurement.new(:value => value, :timestamp => timestamps[index])
210
+ end
211
+ stat.complete
212
+ end
213
+
214
+ def test_seed_measurement
215
+ seed_measurement = mock()
216
+ seed_measurement.stubs(:value).returns(1)
217
+ seed_measurement.stubs(:timestamp).returns(1)
218
+ seed_measurement
219
+ end
220
+
221
+ end
222
+ end