wonkavision 0.5.11 → 0.6.0

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 (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