modesty 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +18 -0
  3. data/LICENSE +21 -0
  4. data/README.md +121 -0
  5. data/Rakefile +29 -0
  6. data/VERSION +1 -0
  7. data/init.rb +1 -0
  8. data/lib/modesty.rb +26 -0
  9. data/lib/modesty/api.rb +14 -0
  10. data/lib/modesty/core_ext.rb +5 -0
  11. data/lib/modesty/core_ext/array.rb +21 -0
  12. data/lib/modesty/core_ext/fixnum.rb +5 -0
  13. data/lib/modesty/core_ext/hash.rb +39 -0
  14. data/lib/modesty/core_ext/string.rb +9 -0
  15. data/lib/modesty/core_ext/symbol.rb +33 -0
  16. data/lib/modesty/datastore.rb +51 -0
  17. data/lib/modesty/datastore/redis.rb +180 -0
  18. data/lib/modesty/experiment.rb +87 -0
  19. data/lib/modesty/experiment/base.rb +47 -0
  20. data/lib/modesty/experiment/builder.rb +48 -0
  21. data/lib/modesty/experiment/console.rb +4 -0
  22. data/lib/modesty/experiment/data.rb +75 -0
  23. data/lib/modesty/experiment/interface.rb +29 -0
  24. data/lib/modesty/experiment/significance.rb +376 -0
  25. data/lib/modesty/experiment/stats.rb +163 -0
  26. data/lib/modesty/frameworks/rails.rb +27 -0
  27. data/lib/modesty/identity.rb +32 -0
  28. data/lib/modesty/load.rb +80 -0
  29. data/lib/modesty/load/load_experiments.rb +14 -0
  30. data/lib/modesty/load/load_metrics.rb +17 -0
  31. data/lib/modesty/metric.rb +56 -0
  32. data/lib/modesty/metric/base.rb +38 -0
  33. data/lib/modesty/metric/builder.rb +23 -0
  34. data/lib/modesty/metric/data.rb +133 -0
  35. data/modesty.gemspec +192 -0
  36. data/spec/core_ext_spec.rb +17 -0
  37. data/spec/experiment_spec.rb +239 -0
  38. data/spec/identity_spec.rb +161 -0
  39. data/spec/load_spec.rb +87 -0
  40. data/spec/metric_spec.rb +176 -0
  41. data/spec/rails_spec.rb +48 -0
  42. data/spec/redis_spec.rb +29 -0
  43. data/spec/significance_spec.rb +147 -0
  44. data/spec/spec.opts +1 -0
  45. data/test/myapp/config/modesty.yml +9 -0
  46. data/test/myapp/modesty/experiments/cookbook.rb +4 -0
  47. data/test/myapp/modesty/metrics/kitchen_metrics.rb +9 -0
  48. data/test/myapp/modesty/metrics/stove/burner_metrics.rb +2 -0
  49. data/vendor/.piston.yml +8 -0
  50. data/vendor/mock_redis/.gitignore +2 -0
  51. data/vendor/mock_redis/README +8 -0
  52. data/vendor/mock_redis/lib/mock_redis.rb +10 -0
  53. data/vendor/mock_redis/lib/mock_redis/hash.rb +61 -0
  54. data/vendor/mock_redis/lib/mock_redis/list.rb +6 -0
  55. data/vendor/mock_redis/lib/mock_redis/misc.rb +69 -0
  56. data/vendor/mock_redis/lib/mock_redis/set.rb +108 -0
  57. data/vendor/mock_redis/lib/mock_redis/string.rb +32 -0
  58. data/vendor/redis-rb/.gitignore +8 -0
  59. data/vendor/redis-rb/LICENSE +20 -0
  60. data/vendor/redis-rb/README.markdown +129 -0
  61. data/vendor/redis-rb/Rakefile +155 -0
  62. data/vendor/redis-rb/benchmarking/logging.rb +62 -0
  63. data/vendor/redis-rb/benchmarking/pipeline.rb +51 -0
  64. data/vendor/redis-rb/benchmarking/speed.rb +21 -0
  65. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  66. data/vendor/redis-rb/benchmarking/thread_safety.rb +38 -0
  67. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  68. data/vendor/redis-rb/examples/basic.rb +15 -0
  69. data/vendor/redis-rb/examples/dist_redis.rb +43 -0
  70. data/vendor/redis-rb/examples/incr-decr.rb +17 -0
  71. data/vendor/redis-rb/examples/list.rb +26 -0
  72. data/vendor/redis-rb/examples/pubsub.rb +31 -0
  73. data/vendor/redis-rb/examples/sets.rb +36 -0
  74. data/vendor/redis-rb/examples/unicorn/config.ru +3 -0
  75. data/vendor/redis-rb/examples/unicorn/unicorn.rb +20 -0
  76. data/vendor/redis-rb/lib/redis.rb +676 -0
  77. data/vendor/redis-rb/lib/redis/client.rb +201 -0
  78. data/vendor/redis-rb/lib/redis/compat.rb +21 -0
  79. data/vendor/redis-rb/lib/redis/connection.rb +134 -0
  80. data/vendor/redis-rb/lib/redis/distributed.rb +526 -0
  81. data/vendor/redis-rb/lib/redis/hash_ring.rb +131 -0
  82. data/vendor/redis-rb/lib/redis/pipeline.rb +13 -0
  83. data/vendor/redis-rb/lib/redis/subscribe.rb +79 -0
  84. data/vendor/redis-rb/redis.gemspec +29 -0
  85. data/vendor/redis-rb/test/commands_on_hashes_test.rb +46 -0
  86. data/vendor/redis-rb/test/commands_on_lists_test.rb +50 -0
  87. data/vendor/redis-rb/test/commands_on_sets_test.rb +78 -0
  88. data/vendor/redis-rb/test/commands_on_sorted_sets_test.rb +109 -0
  89. data/vendor/redis-rb/test/commands_on_strings_test.rb +70 -0
  90. data/vendor/redis-rb/test/commands_on_value_types_test.rb +88 -0
  91. data/vendor/redis-rb/test/connection_handling_test.rb +87 -0
  92. data/vendor/redis-rb/test/db/.gitignore +1 -0
  93. data/vendor/redis-rb/test/distributd_key_tags_test.rb +53 -0
  94. data/vendor/redis-rb/test/distributed_blocking_commands_test.rb +54 -0
  95. data/vendor/redis-rb/test/distributed_commands_on_hashes_test.rb +12 -0
  96. data/vendor/redis-rb/test/distributed_commands_on_lists_test.rb +18 -0
  97. data/vendor/redis-rb/test/distributed_commands_on_sets_test.rb +85 -0
  98. data/vendor/redis-rb/test/distributed_commands_on_strings_test.rb +50 -0
  99. data/vendor/redis-rb/test/distributed_commands_on_value_types_test.rb +73 -0
  100. data/vendor/redis-rb/test/distributed_commands_requiring_clustering_test.rb +141 -0
  101. data/vendor/redis-rb/test/distributed_connection_handling_test.rb +25 -0
  102. data/vendor/redis-rb/test/distributed_internals_test.rb +18 -0
  103. data/vendor/redis-rb/test/distributed_persistence_control_commands_test.rb +24 -0
  104. data/vendor/redis-rb/test/distributed_publish_subscribe_test.rb +90 -0
  105. data/vendor/redis-rb/test/distributed_remote_server_control_commands_test.rb +31 -0
  106. data/vendor/redis-rb/test/distributed_sorting_test.rb +21 -0
  107. data/vendor/redis-rb/test/distributed_test.rb +60 -0
  108. data/vendor/redis-rb/test/distributed_transactions_test.rb +34 -0
  109. data/vendor/redis-rb/test/encoding_test.rb +16 -0
  110. data/vendor/redis-rb/test/helper.rb +86 -0
  111. data/vendor/redis-rb/test/internals_test.rb +27 -0
  112. data/vendor/redis-rb/test/lint/hashes.rb +90 -0
  113. data/vendor/redis-rb/test/lint/internals.rb +53 -0
  114. data/vendor/redis-rb/test/lint/lists.rb +93 -0
  115. data/vendor/redis-rb/test/lint/sets.rb +66 -0
  116. data/vendor/redis-rb/test/lint/sorted_sets.rb +132 -0
  117. data/vendor/redis-rb/test/lint/strings.rb +98 -0
  118. data/vendor/redis-rb/test/lint/value_types.rb +84 -0
  119. data/vendor/redis-rb/test/persistence_control_commands_test.rb +22 -0
  120. data/vendor/redis-rb/test/pipelining_commands_test.rb +78 -0
  121. data/vendor/redis-rb/test/publish_subscribe_test.rb +151 -0
  122. data/vendor/redis-rb/test/redis_mock.rb +64 -0
  123. data/vendor/redis-rb/test/remote_server_control_commands_test.rb +56 -0
  124. data/vendor/redis-rb/test/sorting_test.rb +44 -0
  125. data/vendor/redis-rb/test/test.conf +8 -0
  126. data/vendor/redis-rb/test/thread_safety_test.rb +34 -0
  127. data/vendor/redis-rb/test/transactions_test.rb +91 -0
  128. data/vendor/redis-rb/test/unknown_commands_test.rb +14 -0
  129. data/vendor/redis-rb/test/url_param_test.rb +52 -0
  130. metadata +277 -0
@@ -0,0 +1,29 @@
1
+ require 'modesty'
2
+
3
+ describe "Real Redis" do
4
+ before :each do
5
+ Modesty.metrics.clear
6
+ Modesty.experiments.clear
7
+ end
8
+
9
+ it "can connect to redis" do
10
+ lambda { Modesty.set_store :redis }.should_not raise_error
11
+ Modesty.data.store.should be_an_instance_of Redis
12
+ lambda { Modesty.data.flushdb }.should_not raise_error
13
+ end
14
+
15
+ it "can track metrics in real redis" do
16
+ Modesty.new_metric :foo
17
+ lambda do
18
+ (1..100).each do |i|
19
+ Modesty.track! :foo, 2
20
+ Modesty.metrics[:foo].count.should == i*2
21
+ end
22
+ end.should_not raise_error
23
+ end
24
+
25
+ after :all do
26
+ Modesty.data.flushdb
27
+ Modesty.set_store :redis, :mock => true
28
+ end
29
+ end
@@ -0,0 +1,147 @@
1
+ require 'modesty'
2
+
3
+ describe "Significance" do
4
+ before :each do
5
+ Modesty.data.flushdb
6
+ Modesty.experiments.clear
7
+ Modesty.metrics.clear
8
+
9
+ @foo = Modesty.new_metric :foo
10
+ @bar = Modesty.new_metric :bar
11
+
12
+ @e = Modesty.new_experiment :baz do |e|
13
+ e.metrics :foo, :bar
14
+
15
+ e.conversion :foo_conv, :on => [:foo, :bar]
16
+ e.distribution :foo_dist, :on => :foo
17
+ end
18
+
19
+ @foo_dist = @e.stats[:foo_dist]
20
+ @foo_conv = @e.stats[:foo_conv]
21
+
22
+ Modesty.identify! 1
23
+ end
24
+
25
+ it "handles significant distribution data" do
26
+ 250.times do |uid|
27
+ @e.chooses :experiment, :for => uid
28
+ Modesty.track! :foo, rand(200), :with => {:user => uid}
29
+ end
30
+
31
+ 250.times do |uid|
32
+ uid = 251 + uid
33
+ @e.chooses :control, :for => uid
34
+ Modesty.track! :foo, rand(100), :with => {:user => uid}
35
+ end
36
+
37
+ @foo_dist.should be_a Modesty::Experiment::DistributionStat
38
+ an = @foo_dist.analysis
39
+ an.should be_a Hash
40
+ an[:control][:mean].should be_close(50, 10)
41
+ an[:experiment][:mean].should be_close(100, 20)
42
+ an[:control][:size].should == 250
43
+ an[:experiment][:size].should == 250
44
+
45
+ sig = @foo_dist.significance
46
+ sig.should be_a Float
47
+ sig.should be < 0.01
48
+ @foo_dist.should be_significant
49
+ end
50
+
51
+ it "handles insignificant distribution data" do
52
+ @e.chooses :experiment
53
+ 250.times do
54
+ Modesty.track! :foo, 1+rand(10)
55
+ end
56
+ @e.chooses :control
57
+ 250.times do
58
+ Modesty.track! :foo, 1+rand(10)
59
+ end
60
+
61
+ sig = @foo_dist.significance
62
+ sig.should be_nil
63
+ @foo_dist.should_not be_significant
64
+ end
65
+
66
+ it "handles significant conversion data" do
67
+ @e.chooses :experiment
68
+ 500.times do
69
+ Modesty.track! :foo, 1+rand(5)
70
+ Modesty.track! :bar, 100
71
+ end
72
+
73
+ @e.chooses :control
74
+ 500.times do
75
+ Modesty.track! :foo, 1+rand(100)
76
+ Modesty.track! :bar, 100
77
+ end
78
+
79
+ @foo_conv.should be_a Modesty::Experiment::ConversionStat
80
+ sig = @foo_conv.significance
81
+ sig.should_not be_nil
82
+ end
83
+ end
84
+
85
+ describe "Statistics with blocks" do
86
+ before :each do
87
+ Modesty.data.flushdb
88
+ Modesty.metrics.clear
89
+ Modesty.experiments.clear
90
+
91
+ Modesty.new_metric :foo
92
+ Modesty.new_metric :bar
93
+
94
+ @e = Modesty.new_experiment :baz do |e|
95
+ e.metrics :foo, :bar
96
+
97
+ e.distribution :special_dist do |metrics|
98
+ metrics[:foo].distribution + metrics[:bar].distribution
99
+ end
100
+
101
+ e.conversion :special_conv do |metrics|
102
+ [
103
+ metrics[:foo].unique(:users),
104
+ metrics[:foo].count
105
+ ]
106
+ end
107
+ end
108
+
109
+ (1..500).each do |i|
110
+ Modesty.identify!(i)
111
+ Modesty.group :baz
112
+ Modesty.track! :foo, 1+rand(i)
113
+ Modesty.track! :bar, 1+rand(501-i)
114
+ end
115
+
116
+ end
117
+
118
+ it "uses the blocks for distribution" do
119
+ three_days = @e.stats[:special_dist].data(3.days.ago..Date.today)
120
+ three_days.should == {
121
+ :control => (
122
+ @e.metrics(:control)[:foo].distribution(3.days.ago..Date.today).sum +
123
+ @e.metrics(:control)[:bar].distribution(3.days.ago, Date.today).sum
124
+ ),
125
+ :experiment => (
126
+ @e.metrics(:experiment)[:foo].distribution(3.days.ago, :today).sum +
127
+ @e.metrics(:experiment)[:bar].distribution(3.days.ago..Date.today).sum
128
+ )
129
+ }
130
+
131
+ three_days.should == @e.stats[:special_dist].data(3.days.ago, :today)
132
+ end
133
+
134
+ it "uses the blocks for conversion" do
135
+ three_days = @e.stats[:special_conv].data(3.days.ago..Date.today)
136
+ three_days.sort.should == [
137
+ [
138
+ @e.metrics(:experiment)[:foo].unique(:users, 3.days.ago, :today).sum,
139
+ @e.metrics(:experiment)[:foo].count(3.days.ago, Date.today).sum
140
+ ],
141
+ [
142
+ @e.metrics(:control)[:foo].unique(:users, 3.days.ago..Date.today).sum,
143
+ @e.metrics(:control)[:foo].count(3.days.ago, :today).sum
144
+ ],
145
+ ].sort
146
+ end
147
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ test:
2
+ datastore:
3
+ type: redis
4
+ port: 6379
5
+ host: localhost
6
+
7
+ paths:
8
+ experiments: experiments
9
+ metrics: metrics
@@ -0,0 +1,4 @@
1
+ Modesty.new_experiment :cookbook do |e|
2
+ e.alternatives :big, :medium, :small
3
+ e.metrics :baked_goods/:cookies, :baked_goods/:brownies, :baked_goods/:cake
4
+ end
@@ -0,0 +1,9 @@
1
+ Modesty.new_metric :baked_goods do |m|
2
+ m.description "Yummy baked things"
3
+ m.submetric :cookies
4
+ m.submetric :brownies
5
+ m.submetric :cake do |m|
6
+ m.submetric :chocolate
7
+ m.submetric :ice_cream
8
+ end
9
+ end
@@ -0,0 +1,2 @@
1
+ Modesty.new_metric :burner
2
+ Modesty.new_metric :griddle
@@ -0,0 +1,8 @@
1
+ ---
2
+ repository_url: git://github.com/ezmobius/redis-rb.git
3
+ lock: false
4
+ repository_class: Piston::Git::Repository
5
+ format: 1
6
+ handler:
7
+ commit: 283b58be1fbbed401dba36e1b9f4fa0d8be7904d
8
+ branch: master
@@ -0,0 +1,2 @@
1
+ .*.swo
2
+ .*.swp
@@ -0,0 +1,8 @@
1
+ Usage:
2
+
3
+ r = MockRedis.new(:some, :cool => :options)
4
+
5
+ now pretend r is a redis client. see github.com/ezmobius/redis-rb.
6
+ DO NOT USE IN PRODUCTION. PLZ. KTHX
7
+
8
+ This was based on a random file in assaf's "vanity", but as I extended it, it seemed like it needed its own repo.
@@ -0,0 +1,10 @@
1
+ class MockRedis
2
+ LIB_DIR = File.dirname(__FILE__)
3
+ end
4
+
5
+ $:.unshift MockRedis::LIB_DIR
6
+ require 'mock_redis/misc.rb'
7
+ require 'mock_redis/string.rb'
8
+ require 'mock_redis/set.rb'
9
+ require 'mock_redis/hash.rb'
10
+ $:.shift
@@ -0,0 +1,61 @@
1
+ class MockRedis
2
+ module HashMethods
3
+ def hset(key, hkey, val)
4
+ case h = self.hash[key]
5
+ when nil then self.hash[key] = {hkey => val.to_s}
6
+ when Hash then h[hkey] = val.to_s
7
+ else fail "Not a hash"
8
+ end
9
+ end
10
+
11
+ def hget(key, hkey)
12
+ case h = self.hash[key]
13
+ when nil then nil
14
+ when Hash then h[hkey]
15
+ else fail "Not a hash"
16
+ end
17
+ end
18
+
19
+ def hincrby(key, hkey, val)
20
+ case h = self.hash[key]
21
+ when nil then self.hash[key] = {hkey => val.to_s}
22
+ when Hash then h[hkey] = (h[hkey].to_i + val).to_s
23
+ else fail "Not a hash"
24
+ end
25
+ end
26
+
27
+ def hgetall(key)
28
+ case h = self.hash[key]
29
+ when nil then {}
30
+ when Hash then h
31
+ else fail "Not a hash"
32
+ end
33
+ end
34
+
35
+ def hkeys(key)
36
+ case h = self.hash[key]
37
+ when nil then []
38
+ when Hash then h.keys
39
+ else fail "Not a hash"
40
+ end
41
+ end
42
+
43
+ def hvals(key)
44
+ case h = self.hash[key]
45
+ when nil then []
46
+ when Hash then h.values
47
+ else fail "Not a hash"
48
+ end
49
+ end
50
+
51
+ def hlen(key)
52
+ case h = self.hash[key]
53
+ when nil then 0
54
+ when Hash then h.keys.count
55
+ else fail "Not a hash"
56
+ end
57
+ end
58
+ end
59
+
60
+ include HashMethods
61
+ end
@@ -0,0 +1,6 @@
1
+ class MockRedis
2
+ module ListMethods
3
+ end
4
+
5
+ include ListMethods
6
+ end
@@ -0,0 +1,69 @@
1
+ class MockRedis
2
+ module MiscMethods
3
+ def hash
4
+ @@hash ||= {}
5
+ end
6
+
7
+ def del(*keys)
8
+ keys.flatten.each do |key|
9
+ self.hash.delete key
10
+ end
11
+ "OK"
12
+ end
13
+
14
+ def exists(key)
15
+ self.hash.has_key?(key)
16
+ end
17
+
18
+ def type(key)
19
+ case thing = self.hash[key]
20
+ when nil then "none"
21
+ when String then "string"
22
+ when Array then "list"
23
+ when Set then "set"
24
+ when Hash then "hash"
25
+ end
26
+ end
27
+
28
+ def rename(old, new)
29
+ self.hash[new] = self.hash[old]
30
+ self.hash.delete(old)
31
+ end
32
+
33
+ def renamenx(old, new)
34
+ rename(old, new) unless exists(new)
35
+ end
36
+
37
+ def keys(pattern)
38
+ regexp = Regexp.new(pattern.split("*").map { |r| Regexp.escape(r) }.join(".*"))
39
+ self.hash.keys.select { |key| key =~ regexp }
40
+ end
41
+
42
+ def randomkey
43
+ self.hash.keys[rand(dbsize)]
44
+ end
45
+
46
+ def dbsize
47
+ self.hash.keys.count
48
+ end
49
+
50
+ def flushdb
51
+ self.hash.clear
52
+ end
53
+ alias flushall flushdb
54
+
55
+ def multi
56
+ yield if block_given?
57
+ end
58
+
59
+ def ping
60
+ "PONG"
61
+ end
62
+
63
+ def pipelined
64
+ yield
65
+ end
66
+ end
67
+
68
+ include MiscMethods
69
+ end
@@ -0,0 +1,108 @@
1
+ class MockRedis
2
+ require 'set'
3
+
4
+ module SetMethods
5
+ def sadd(key, value)
6
+ fail_unless_set(key)
7
+ value = value.to_s
8
+ case set = self.hash[key]
9
+ when nil then self.hash[key] = Set.new([value])
10
+ when Set then set.add value
11
+ end
12
+ end
13
+
14
+ def srem(key, value)
15
+ value = value.to_s
16
+ fail_unless_set(key)
17
+ case set = self.hash[key]
18
+ when nil then return
19
+ when Set then set.delete(value)
20
+ end
21
+ end
22
+
23
+ def sismember(key, value)
24
+ fail_unless_set(key)
25
+ case set = self.hash[key]
26
+ when nil then return false ; puts "no set here"
27
+ when Set then set.include?(value.to_s)
28
+ end
29
+ end
30
+
31
+ def spop(key, val)
32
+ fail_unless_set(key)
33
+ case set = self.hash[key]
34
+ when nil then nil
35
+ when Set
36
+ el = set.to_a[rand(set.size)]
37
+ set.delete el
38
+ el
39
+ end
40
+ end
41
+
42
+ def smove(src_key, dst_key, member)
43
+ fail_unless_set(dst_key)
44
+ if el = self.srem(src_key, member)
45
+ self.sadd(dst_key, member)
46
+ end
47
+ end
48
+
49
+ def srandmember(key)
50
+ fail_unless_set(key)
51
+ case set = self.hash[key]
52
+ when nil then nil
53
+ when Set then set.to_a[rand(set.size)]
54
+ end
55
+ end
56
+
57
+ def smembers(key)
58
+ fail_unless_set(key)
59
+ case set = self.hash[key]
60
+ when nil then []
61
+ when Set then set.to_a
62
+ end
63
+ end
64
+
65
+ def scard(key)
66
+ fail_unless_set(key)
67
+ case set = self.hash[key]
68
+ when nil then 0
69
+ when Set then set.size
70
+ end
71
+ end
72
+
73
+ def sinter(*keys)
74
+ keys.each { |k| fail_unless_set(k) }
75
+ return Set.new if keys.any? { |k| self.hash[k].nil? }
76
+ keys.inject do |set, key|
77
+ set & self.hash[key]
78
+ end
79
+ end
80
+
81
+ def sunion(*keys)
82
+ keys.each { |k| fail_unless_set(k) }
83
+ keys.inject(Set.new) do |set, key|
84
+ return set if self.hash[key].nil?
85
+ set | self.hash[key]
86
+ end
87
+ end
88
+
89
+ def sdiff(first, *others)
90
+ [first, *others].each { |k| fail_unless_set(k) }
91
+ others = others.map { |k| self.hash[k] || Set.new }
92
+ others.inject(first) do |memo, set|
93
+ memo - self.hash[set]
94
+ end
95
+ end
96
+
97
+ private
98
+ def is_a_set?(key)
99
+ self.hash[key].is_a?(Set) || self.hash[key].nil?
100
+ end
101
+
102
+ def fail_unless_set(key)
103
+ fail "Not a set" unless is_a_set?(key)
104
+ end
105
+ end
106
+
107
+ include SetMethods
108
+ end