wonkavision 0.5.11 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG.rdoc +3 -0
  2. data/lib/wonkavision.rb +28 -1
  3. data/lib/wonkavision/aggregation.rb +21 -0
  4. data/lib/wonkavision/event_coordinator.rb +19 -7
  5. data/lib/wonkavision/extensions/symbol.rb +55 -0
  6. data/lib/wonkavision/facts.rb +27 -0
  7. data/lib/wonkavision/local_job_queue.rb +28 -0
  8. data/lib/wonkavision/message_mapper.rb +2 -2
  9. data/lib/wonkavision/message_mapper/map.rb +60 -8
  10. data/lib/wonkavision/persistence/mongo.rb +95 -0
  11. data/lib/wonkavision/plugins.rb +2 -1
  12. data/lib/wonkavision/plugins/analytics/aggregation.rb +139 -0
  13. data/lib/wonkavision/plugins/analytics/aggregation/aggregation_spec.rb +53 -0
  14. data/lib/wonkavision/plugins/analytics/aggregation/attribute.rb +22 -0
  15. data/lib/wonkavision/plugins/analytics/aggregation/dimension.rb +64 -0
  16. data/lib/wonkavision/plugins/analytics/aggregation/measure.rb +240 -0
  17. data/lib/wonkavision/plugins/analytics/cellset.rb +171 -0
  18. data/lib/wonkavision/plugins/analytics/facts.rb +106 -0
  19. data/lib/wonkavision/plugins/analytics/handlers/apply_aggregation.rb +35 -0
  20. data/lib/wonkavision/plugins/analytics/handlers/split_by_aggregation.rb +60 -0
  21. data/lib/wonkavision/plugins/analytics/member_filter.rb +106 -0
  22. data/lib/wonkavision/plugins/analytics/mongo.rb +6 -0
  23. data/lib/wonkavision/plugins/analytics/persistence/hash_store.rb +59 -0
  24. data/lib/wonkavision/plugins/analytics/persistence/mongo_store.rb +85 -0
  25. data/lib/wonkavision/plugins/analytics/persistence/store.rb +105 -0
  26. data/lib/wonkavision/plugins/analytics/query.rb +76 -0
  27. data/lib/wonkavision/plugins/event_handling.rb +15 -3
  28. data/lib/wonkavision/version.rb +1 -1
  29. data/test/aggregation_spec_test.rb +99 -0
  30. data/test/aggregation_test.rb +170 -0
  31. data/test/analytics/test_aggregation.rb +78 -0
  32. data/test/apply_aggregation_test.rb +92 -0
  33. data/test/attribute_test.rb +26 -0
  34. data/test/cellset_test.rb +200 -0
  35. data/test/dimension_test.rb +186 -0
  36. data/test/facts_test.rb +146 -0
  37. data/test/hash_store_test.rb +112 -0
  38. data/test/log/test.log +96844 -0
  39. data/test/map_test.rb +48 -1
  40. data/test/measure_test.rb +146 -0
  41. data/test/member_filter_test.rb +143 -0
  42. data/test/mongo_store_test.rb +115 -0
  43. data/test/query_test.rb +106 -0
  44. data/test/split_by_aggregation_test.rb +114 -0
  45. data/test/store_test.rb +71 -0
  46. data/test/symbol_test.rb +62 -0
  47. data/test/test_activity_models.rb +1 -1
  48. data/test/test_aggregation.rb +42 -0
  49. data/test/test_data.tuples +100 -0
  50. data/test/test_helper.rb +7 -0
  51. metadata +57 -5
@@ -112,7 +112,7 @@ class MapTest < ActiveSupport::TestCase
112
112
  should "accept a time unmolested" do
113
113
  time = Time.now
114
114
  m = Wonkavision::MessageMapper::Map.new(:a=>time)
115
- m.date :a
115
+ m.time :a
116
116
  assert_equal time, m.a
117
117
  end
118
118
  end
@@ -311,5 +311,52 @@ class MapTest < ActiveSupport::TestCase
311
311
  assert_equal 4, m.new_collection[1].b
312
312
  end
313
313
  end
314
+
315
+ context "Map.duration" do
316
+ should "return nil if no :from or :to value is available" do
317
+ m = Wonkavision::MessageMapper::Map.new
318
+ m.duration :a_duration
319
+ assert_nil m.a_duration
320
+ end
321
+ should "return time from Time.now if only to is supplied" do
322
+ m = Wonkavision::MessageMapper::Map.new({ :a => "1/1/2001" })
323
+ m.time :a
324
+ m.duration :a_duration, :to => m.a
325
+ assert m.a_duration < 0
326
+ end
327
+ should "return time to Time.now if only from is supplied" do
328
+ m = Wonkavision::MessageMapper::Map.new({ :a=>"1/1/2001"})
329
+ m.time :a
330
+ m.duration :a_duration, :from => m.a
331
+ assert m.a_duration > 0
332
+ end
333
+ should "convert the duration to the desired time unit" do
334
+ m = Wonkavision::MessageMapper::Map.new
335
+ m.expects(:convert_seconds).with(1,:months)
336
+ m.time :a=>"1/1/2001 00:00:00"
337
+ m.time :b=>"1/1/2001 00:00:01"
338
+ m.duration :a_duration, :from=>m.a, :to=>m.b, :in=>:months
339
+ end
340
+ end
341
+ context "Map.convert_seconds" do
342
+ setup do
343
+ @m = Wonkavision::MessageMapper::Map.new
344
+ end
345
+ should "raise an exception for an invalid unit" do
346
+ assert_raise(RuntimeError) { @m.send(:convert_seconds,1,:wakka)}
347
+ end
348
+ should "should correctly calculate proper units" do
349
+ assert_equal 100, @m.send(:convert_seconds,100,:seconds)
350
+ assert_equal 1, @m.send(:convert_seconds,60,:minutes)
351
+ assert_equal 1, @m.send(:convert_seconds,60*60,:hours)
352
+ assert_equal 1, @m.send(:convert_seconds,60*60*24,:days)
353
+ assert_equal 1, @m.send(:convert_seconds,60*60*24*7,:weeks)
354
+ assert_equal 1, @m.send(:convert_seconds,60*60*24*30,:months)
355
+ assert_equal 1, @m.send(:convert_seconds,60*60*24*365,:years)
356
+ end
357
+
358
+ end
359
+
360
+
314
361
  end
315
362
  end
@@ -0,0 +1,146 @@
1
+ require "test_helper"
2
+
3
+ class MeasureTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @stats = Wonkavision::Plugins::Aggregation::Measure.new(:log_buckets => 128)
7
+
8
+ @@DATA.each do |x|
9
+ @stats << x
10
+ end
11
+ end
12
+
13
+ def test_stats_count
14
+ assert_equal @@DATA.length, @stats.count
15
+ end
16
+
17
+ def test_stats_min_max
18
+ sorted_data = @@DATA.sort
19
+
20
+ assert_equal sorted_data[0], @stats.min
21
+ assert_equal sorted_data.last, @stats.max
22
+ end
23
+
24
+ def test_stats_mean
25
+ sum = 0
26
+ @@DATA.each do |x|
27
+ sum += x
28
+ end
29
+
30
+ assert_equal sum.to_f/@@DATA.length.to_f, @stats.mean
31
+ end
32
+
33
+ def test_bucket_counts
34
+
35
+ #Test each iterator
36
+ total_bucket_sum = 0
37
+ i = 0
38
+ @stats.each do |bucket, count|
39
+ assert_equal 2**i, bucket
40
+
41
+ total_bucket_sum += count
42
+ i += 1
43
+ end
44
+
45
+ assert_equal @@DATA.length, total_bucket_sum
46
+
47
+ #Test each_nonzero iterator
48
+ prev_bucket = 0
49
+ total_bucket_sum = 0
50
+ @stats.each_nonzero do |bucket, count|
51
+ assert bucket > prev_bucket
52
+ assert_not_equal count, 0
53
+
54
+ total_bucket_sum += count
55
+ end
56
+
57
+ assert_equal total_bucket_sum, @@DATA.length
58
+ end
59
+
60
+
61
+ #XXX: Update test_bucket_contents() if you muck with @@DATA
62
+ @@DATA = [ 1, 5, 4, 6, 1028, 1972, 16384, 16385, 16383]
63
+ def test_bucket_contents
64
+ #XXX: This is the only test so far that cares about the actual contents
65
+ # of @@DATA, so if you update that array ... update this method too
66
+ expected_buckets = [1, 4, 1024, 8192, 16384]
67
+ expected_counts = [1, 3, 2, 1, 2]
68
+
69
+ i = 0
70
+ @stats.each_nonzero do |bucket, count|
71
+ assert_equal expected_buckets[i], bucket
72
+ assert_equal expected_counts[i], count
73
+ # Increment for the next test
74
+ i += 1
75
+ end
76
+ end
77
+
78
+ def test_outlier
79
+ assert_equal 0, @stats.outliers_low
80
+ assert_equal 0, @stats.outliers_high
81
+
82
+ @stats << -1
83
+ @stats << -2
84
+ @stats << 0
85
+
86
+ @stats << 2**128
87
+
88
+ # This should be the last value in the last bucket, but Ruby's native
89
+ # floats are not precise enough. Somewhere past 2^32 the log(x)/log(2)
90
+ # breaks down. So it shows up as 128 (outlier) instead of 127
91
+ #@stats << (2**128) - 1
92
+
93
+ assert_equal 3, @stats.outliers_low
94
+ assert_equal 1, @stats.outliers_high
95
+ end
96
+
97
+ def test_std_dev
98
+ @stats.std_dev
99
+ end
100
+ end
101
+
102
+ class LinearHistogramTest < Test::Unit::TestCase
103
+ def setup
104
+ @stats = Wonkavision::Plugins::Aggregation::Measure.new(:low => 0, :high => 32768, :width => 1024)
105
+
106
+ @@DATA.each do |x|
107
+ @stats << x
108
+ end
109
+ end
110
+
111
+ def test_validation
112
+
113
+ # Range cannot be 0
114
+ assert_raise(ArgumentError) {bad_stats = Wonkavision::Plugins::Aggregation::Measure.new(:low => 32,:high => 32, :width => 4)}
115
+
116
+ # Range cannot be negative
117
+ assert_raise(ArgumentError) {bad_stats = Wonkavision::Plugins::Aggregation::Measure.new(:low => 32, :high => 16, :width => 4)}
118
+
119
+ # Range cannot be < single bucket
120
+ assert_raise(ArgumentError) {bad_stats = Wonkavision::Plugins::Aggregation::Measure.new(:low => 16, :high => 32, :width => 17)}
121
+
122
+ # Range % width must equal 0 (for now)
123
+ assert_raise(ArgumentError) {bad_stats = Wonkavision::Plugins::Aggregation::Measure.new(:low => 1, :high => 16384, :width => 1024)}
124
+ end
125
+
126
+ #XXX: Update test_bucket_contents() if you muck with @@DATA
127
+ # 32768 is an outlier
128
+ @@DATA = [ 0, 1, 5, 4, 6, 1028, 1972, 16384, 16385, 16383, 32768]
129
+ def test_bucket_contents
130
+ #XXX: This is the only test so far that cares about the actual contents
131
+ # of @@DATA, so if you update that array ... update this method too
132
+ expected_buckets = [0, 1024, 15360, 16384]
133
+ expected_counts = [5, 2, 1, 2]
134
+
135
+ i = 0
136
+ @stats.each_nonzero do |bucket, count|
137
+ assert_equal expected_buckets[i], bucket
138
+ assert_equal expected_counts[i], count
139
+ # Increment for the next test
140
+ i += 1
141
+ end
142
+ end
143
+
144
+
145
+
146
+ end
@@ -0,0 +1,143 @@
1
+ require "test_helper"
2
+
3
+ class MemberFilterTest < ActiveSupport::TestCase
4
+ context "MemberFilter" do
5
+ setup do
6
+ @dimension = Wonkavision::Analytics::MemberFilter.new(:a_dim, :member_type=>:dimension)
7
+ @measure = Wonkavision::Analytics::MemberFilter.new(:a_measure, :member_type=>:measure)
8
+ end
9
+ context "#attribute_name" do
10
+ should "default to key for dimension" do
11
+ assert_equal :key, @dimension.attribute_name
12
+ end
13
+ should "default to count for measure" do
14
+ assert_equal :count, @measure.attribute_name
15
+ end
16
+ end
17
+ context "#dimension?" do
18
+ should "be true for dimension" do
19
+ assert @dimension.dimension?
20
+ end
21
+ should "not be true for measure" do
22
+ assert !@measure.dimension?
23
+ end
24
+ end
25
+ context "#measure?" do
26
+ should "be true for measure" do
27
+ assert @measure.measure?
28
+ end
29
+ should "not be true for dimension" do
30
+ assert !@dimension.measure?
31
+ end
32
+ end
33
+ context "#operators" do
34
+ should "set the operator property appropriately" do
35
+ [:gt, :lt, :gte, :lte, :ne, :in, :nin].each do |op|
36
+ assert_equal op, @dimension.send(op).operator
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ context "#matches" do
43
+ setup do
44
+ @tuple = {
45
+ "measures" => { "a_measure" => { "sum" => 2.0, "count" => 5 } }
46
+ }
47
+ @aggregation = {}
48
+ @dimension = {}
49
+ @filter = Wonkavision::Analytics::MemberFilter.new(:a_measure, :member_type=>:measure,:value=>1)
50
+ end
51
+ should "return false for gt, lt, gte, lte if the data is nil" do
52
+ @tuple["measures"]["a_measure"]["count"] = nil
53
+ [:gt, :lt, :gte, :lte].each do |op|
54
+ @filter.send(op)
55
+ assert_equal false, @filter.matches(@aggregation, @tuple)
56
+ end
57
+ end
58
+ should "evaluate gt, gte" do
59
+ [:gt, :gte].each do |op|
60
+ @filter.send(op,4)
61
+ assert @filter.matches(@aggregation, @tuple)
62
+ end
63
+ end
64
+ should "evaluate lt, lte" do
65
+ [:lt, :lte].each do |op|
66
+ @filter.send(op,6)
67
+ assert @filter.matches(@aggregation, @tuple)
68
+ end
69
+ end
70
+ should "evaluate in" do
71
+ @filter.in([4,5,6])
72
+ assert @filter.matches(@aggregation, @tuple)
73
+ end
74
+ should "evalute nin" do
75
+ @filter.nin([6,7,8])
76
+ assert @filter.matches(@aggregation, @tuple)
77
+ end
78
+ should "evaluate eq" do
79
+ @filter.eq(5)
80
+ assert @filter.matches(@aggregation, @tuple)
81
+ end
82
+ should "evaluate ne" do
83
+ @filter.ne(6)
84
+ assert @filter.matches(@aggregation, @tuple)
85
+ end
86
+ end
87
+
88
+ context "#extract_attribute_value_from_tuple" do
89
+ setup do
90
+ @tuple = {
91
+ "dimensions" => { "a_dimension" => { "akey" => "abc" } },
92
+ "measures" => { "a_measure" => { "sum" => 2.0, "count" => 5 } }
93
+ }
94
+ @aggregation = {}
95
+ @dimension = {}
96
+ end
97
+ context "for a dimension filter" do
98
+ setup do
99
+ @filter = Wonkavision::Analytics::MemberFilter.new(:a_dimension)
100
+ @aggregation.expects(:dimensions).returns( { :a_dimension => @dimension } )
101
+ end
102
+ should "extract a dimension value from a tuple message" do
103
+ @dimension.expects(:key).returns(:akey)
104
+ assert_equal "abc", @filter.send(:extract_attribute_value_from_tuple,@aggregation,@tuple)
105
+ end
106
+ should "extract a dimension value with a custom attribute" do
107
+ @filter.instance_variable_set("@attribute_name","akey")
108
+ assert_equal "abc", @filter.send(:extract_attribute_value_from_tuple,@aggregation,@tuple)
109
+ end
110
+ end
111
+ context "for a measure filter" do
112
+ setup do
113
+ @filter = Wonkavision::Analytics::MemberFilter.new(:a_measure, :member_type=>:measure)
114
+ end
115
+ should "extract a measure value" do
116
+ assert_equal 5, @filter.send(:extract_attribute_value_from_tuple,@aggregation,@tuple)
117
+ end
118
+ end
119
+ end
120
+
121
+ context "#assert_operator_matches_value" do
122
+ should "raise an exception if inappropriately nil" do
123
+ filter = Wonkavision::Analytics::MemberFilter.new(:hi)
124
+ [:gt, :lt, :gte, :lte, :in, :nin].each do |op|
125
+ filter.send(op)
126
+ assert_raise(RuntimeError) { filter.send :assert_operator_matches_value }
127
+ end
128
+ end
129
+ should "raise an exception if 'in' and 'nin' are not given an array" do
130
+ filter = Wonkavision::Analytics::MemberFilter.new(:hi, :value=>:ho)
131
+ [:in,:nin].each do |op|
132
+ filter.send(op)
133
+ assert_raise(RuntimeError) { filter.send :assert_operator_matches_value }
134
+ end
135
+ end
136
+ should "not raise an exception otherwise" do
137
+ filter = Wonkavision::Analytics::MemberFilter.new(:hi, :operator=>:gt, :value=>1)
138
+ filter.send(:assert_operator_matches_value)
139
+ end
140
+ end
141
+
142
+ end
143
+ end
@@ -0,0 +1,115 @@
1
+ require "test_helper"
2
+
3
+ class MongoStoreTest < ActiveSupport::TestCase
4
+ MongoStore = Wonkavision::Analytics::Persistence::MongoStore
5
+
6
+ context "MongoStore" do
7
+ setup do
8
+ @facts = Class.new
9
+ @facts.class_eval do
10
+ def self.name; "TestFacts"; end
11
+ include Wonkavision::Facts
12
+ record_id :tada
13
+ end
14
+ @store = MongoStore.new(@facts)
15
+ end
16
+
17
+ should "provide access to the underlying facts specification" do
18
+ assert_equal @facts, @store.owner
19
+ end
20
+
21
+ should "create a collection name based on the facts class name" do
22
+ assert_equal "wv.test_facts.facts", @store.facts_collection_name
23
+ end
24
+
25
+
26
+ context "Facts persistence" do
27
+ setup do
28
+ @doc_id = 123
29
+ @store.facts_collection.insert( {"_id" => @doc_id,
30
+ "tada" => @doc_id,
31
+ "todo" => "hoho",
32
+ "canttouchthis"=>"yo"} )
33
+ end
34
+ context "#update_facts_record" do
35
+ setup do
36
+ @prev,@cur = @store.send( :update_facts_record,
37
+ @doc_id, "tada"=>@doc_id, "todo"=>"heehee", "more"=>"4me?" )
38
+ end
39
+ should "return the previous version of the facts record" do
40
+ assert_equal( { "tada" => @doc_id, "todo" => "hoho", "canttouchthis"=>"yo"}, @prev )
41
+ end
42
+ should "return the updated version of the facts record" do
43
+ assert_equal({ "tada" => @doc_id, "todo" => "heehee", "canttouchthis"=>"yo","more"=>"4me?"},
44
+ @cur)
45
+ end
46
+ should "contain the upated version in the storage" do
47
+ assert_equal({ "_id"=>@doc_id, "tada" => @doc_id, "todo" => "heehee", "canttouchthis"=>"yo","more"=>"4me?"},
48
+ @store[@doc_id])
49
+ end
50
+ end
51
+ context "#insert_facts_record" do
52
+ setup do
53
+ @cur = @store.send(:insert_facts_record,@doc_id,{ "tada"=>@doc_id,"i is"=>"new"})
54
+ end
55
+ should "return the current version of the facts record" do
56
+ assert_equal( { "tada"=>@doc_id,"i is"=>"new"}, @cur )
57
+ end
58
+ should "add the record to the storage" do
59
+ assert_equal @cur.merge("_id"=>@doc_id), @store[@doc_id]
60
+ end
61
+ end
62
+ context "#delete_facts_record" do
63
+ setup do
64
+ @prev = @store.send(:delete_facts_record,@doc_id,{"tada"=>@doc_id})
65
+ end
66
+ should "return the previous version of the facts record" do
67
+ assert_equal( { "tada" => @doc_id, "todo" => "hoho", "canttouchthis"=>"yo"}, @prev )
68
+ end
69
+ should "remove the facts record from the storage" do
70
+ assert_nil @store[@doc_id]
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ context "Aggregations persistence" do
77
+ setup do
78
+ @tuple = { :dimension_keys=>[1,2,3],
79
+ :dimension_names=>[:a,:b,:c],
80
+ :dimensions=>{"dims"=>"doms"},
81
+ :measures=>{ "measures.one"=>1} }
82
+ end
83
+ context "#update_tuple" do
84
+ should "insert a new tuple if not present" do
85
+ @store.send(:update_tuple, @tuple)
86
+ added = @store.aggregations_collection.find({ :dimension_names=>[:a,:b,:c] }).to_a[0]
87
+ added.delete("_id") #isn't present on the original, we know its there
88
+ assert_equal @tuple.merge({:measures=>{ "one" => 1}}).stringify_keys!, added
89
+ end
90
+ end
91
+ context "#append_filters" do
92
+ setup do
93
+ @dim_filter = Wonkavision::Analytics::MemberFilter.new("tada",:value=>[1,2,3])
94
+ @measure_filter = Wonkavision::Analytics::MemberFilter.new("haha",
95
+ :member_type=>:measure,
96
+ :op=>:gte,
97
+ :value=>100.0)
98
+ @criteria = {}
99
+ @dim_filter.expects(:attribute_key).returns "tada_key"
100
+ @store.send(:append_filters,@criteria,[@dim_filter,@measure_filter])
101
+ end
102
+ should "prepare the dimension filter for mongodb" do
103
+ assert_equal( [1,2,3], @criteria["dimensions.tada.tada_key"] )
104
+ end
105
+ should "prepare the measure filter for mongodb" do
106
+ assert_equal( { "$gte" => 100.0 }, @criteria["measures.haha.count"] )
107
+ end
108
+
109
+
110
+ end
111
+
112
+ end
113
+
114
+ end
115
+ end