mikeg-vanity 1.3.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 (66) hide show
  1. data/CHANGELOG +153 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.rdoc +83 -0
  4. data/bin/vanity +53 -0
  5. data/lib/vanity.rb +38 -0
  6. data/lib/vanity/backport.rb +43 -0
  7. data/lib/vanity/commands.rb +2 -0
  8. data/lib/vanity/commands/list.rb +21 -0
  9. data/lib/vanity/commands/report.rb +60 -0
  10. data/lib/vanity/experiment/ab_test.rb +477 -0
  11. data/lib/vanity/experiment/base.rb +212 -0
  12. data/lib/vanity/helpers.rb +59 -0
  13. data/lib/vanity/metric/active_record.rb +77 -0
  14. data/lib/vanity/metric/base.rb +221 -0
  15. data/lib/vanity/metric/google_analytics.rb +70 -0
  16. data/lib/vanity/mock_redis.rb +76 -0
  17. data/lib/vanity/playground.rb +197 -0
  18. data/lib/vanity/rails.rb +22 -0
  19. data/lib/vanity/rails/dashboard.rb +24 -0
  20. data/lib/vanity/rails/helpers.rb +158 -0
  21. data/lib/vanity/rails/testing.rb +11 -0
  22. data/lib/vanity/templates/_ab_test.erb +26 -0
  23. data/lib/vanity/templates/_experiment.erb +5 -0
  24. data/lib/vanity/templates/_experiments.erb +7 -0
  25. data/lib/vanity/templates/_metric.erb +14 -0
  26. data/lib/vanity/templates/_metrics.erb +13 -0
  27. data/lib/vanity/templates/_report.erb +27 -0
  28. data/lib/vanity/templates/flot.min.js +1 -0
  29. data/lib/vanity/templates/jquery.min.js +19 -0
  30. data/lib/vanity/templates/vanity.css +26 -0
  31. data/lib/vanity/templates/vanity.js +82 -0
  32. data/test/ab_test_test.rb +656 -0
  33. data/test/experiment_test.rb +136 -0
  34. data/test/experiments/age_and_zipcode.rb +19 -0
  35. data/test/experiments/metrics/cheers.rb +3 -0
  36. data/test/experiments/metrics/signups.rb +2 -0
  37. data/test/experiments/metrics/yawns.rb +3 -0
  38. data/test/experiments/null_abc.rb +5 -0
  39. data/test/metric_test.rb +518 -0
  40. data/test/playground_test.rb +10 -0
  41. data/test/rails_test.rb +104 -0
  42. data/test/test_helper.rb +135 -0
  43. data/vanity.gemspec +18 -0
  44. data/vendor/redis-rb/LICENSE +20 -0
  45. data/vendor/redis-rb/README.markdown +36 -0
  46. data/vendor/redis-rb/Rakefile +62 -0
  47. data/vendor/redis-rb/bench.rb +44 -0
  48. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  49. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  50. data/vendor/redis-rb/bin/distredis +33 -0
  51. data/vendor/redis-rb/examples/basic.rb +16 -0
  52. data/vendor/redis-rb/examples/incr-decr.rb +18 -0
  53. data/vendor/redis-rb/examples/list.rb +26 -0
  54. data/vendor/redis-rb/examples/sets.rb +36 -0
  55. data/vendor/redis-rb/lib/dist_redis.rb +124 -0
  56. data/vendor/redis-rb/lib/hash_ring.rb +128 -0
  57. data/vendor/redis-rb/lib/pipeline.rb +21 -0
  58. data/vendor/redis-rb/lib/redis.rb +370 -0
  59. data/vendor/redis-rb/lib/redis/raketasks.rb +1 -0
  60. data/vendor/redis-rb/profile.rb +22 -0
  61. data/vendor/redis-rb/redis-rb.gemspec +30 -0
  62. data/vendor/redis-rb/spec/redis_spec.rb +637 -0
  63. data/vendor/redis-rb/spec/spec_helper.rb +4 -0
  64. data/vendor/redis-rb/speed.rb +16 -0
  65. data/vendor/redis-rb/tasks/redis.tasks.rb +140 -0
  66. metadata +125 -0
@@ -0,0 +1,71 @@
1
+ BENCHMARK_ROOT = File.dirname(__FILE__)
2
+ REDIS_ROOT = File.join(BENCHMARK_ROOT, "..", "lib")
3
+
4
+ $: << REDIS_ROOT
5
+ require 'redis'
6
+ require 'benchmark'
7
+
8
+ def show_usage
9
+ puts <<-EOL
10
+ Usage: worker.rb [read:write] <start_index> <end_index> <sleep_msec>
11
+ EOL
12
+ end
13
+
14
+ def shift_from_argv
15
+ value = ARGV.shift
16
+ unless value
17
+ show_usage
18
+ exit -1
19
+ end
20
+ value
21
+ end
22
+
23
+ operation = shift_from_argv.to_sym
24
+ start_index = shift_from_argv.to_i
25
+ end_index = shift_from_argv.to_i
26
+ sleep_msec = shift_from_argv.to_i
27
+ sleep_duration = sleep_msec/1000.0
28
+
29
+ redis = Redis.new
30
+
31
+ case operation
32
+ when :initialize
33
+
34
+ start_index.upto(end_index) do |i|
35
+ redis[i] = 0
36
+ end
37
+
38
+ when :clear
39
+
40
+ start_index.upto(end_index) do |i|
41
+ redis.delete(i)
42
+ end
43
+
44
+ when :read, :write
45
+
46
+ puts "Starting to #{operation} at segment #{end_index + 1}"
47
+
48
+ loop do
49
+ t1 = Time.now
50
+ start_index.upto(end_index) do |i|
51
+ case operation
52
+ when :read
53
+ redis.get(i)
54
+ when :write
55
+ redis.incr(i)
56
+ else
57
+ raise "Unknown operation: #{operation}"
58
+ end
59
+ sleep sleep_duration
60
+ end
61
+ t2 = Time.now
62
+
63
+ requests_processed = end_index - start_index
64
+ time = t2 - t1
65
+ puts "#{t2.strftime("%H:%M")} [segment #{end_index + 1}] : Processed #{requests_processed} requests in #{time} seconds - #{(requests_processed/time).round} requests/sec"
66
+ end
67
+
68
+ else
69
+ raise "Unknown operation: #{operation}"
70
+ end
71
+
@@ -0,0 +1,33 @@
1
+ require 'fileutils'
2
+
3
+ class RedisCluster
4
+
5
+ def initialize(opts={})
6
+ opts = {:port => 6379, :host => 'localhost', :basedir => "#{Dir.pwd}/rdsrv" }.merge(opts)
7
+ FileUtils.mkdir_p opts[:basedir]
8
+ opts[:size].times do |i|
9
+ port = opts[:port] + i
10
+ FileUtils.mkdir_p "#{opts[:basedir]}/#{port}"
11
+ File.open("#{opts[:basedir]}/#{port}.conf", 'w'){|f| f.write(make_config(port, "#{opts[:basedir]}/#{port}", "#{opts[:basedir]}/#{port}.log"))}
12
+ system(%Q{#{File.join(File.expand_path(File.dirname(__FILE__)), "../redis/redis-server #{opts[:basedir]}/#{port}.conf &" )}})
13
+ end
14
+ end
15
+
16
+ def make_config(port=6379, data=port, logfile='stdout', loglevel='debug')
17
+ config = %Q{
18
+ timeout 300
19
+ save 900 1
20
+ save 300 10
21
+ save 60 10000
22
+ dir #{data}
23
+ loglevel #{loglevel}
24
+ logfile #{logfile}
25
+ databases 16
26
+ port #{port}
27
+ }
28
+ end
29
+
30
+ end
31
+
32
+
33
+ RedisCluster.new :size => 4
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete('foo')
7
+
8
+ puts
9
+
10
+ p'set foo to "bar"'
11
+ r['foo'] = 'bar'
12
+
13
+ puts
14
+
15
+ p 'value of foo'
16
+ p r['foo']
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ puts
7
+ p 'incr'
8
+ r.delete 'counter'
9
+
10
+ p r.incr('counter')
11
+ p r.incr('counter')
12
+ p r.incr('counter')
13
+
14
+ puts
15
+ p 'decr'
16
+ p r.decr('counter')
17
+ p r.decr('counter')
18
+ p r.decr('counter')
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete 'logs'
7
+
8
+ puts
9
+
10
+ p "pushing log messages into a LIST"
11
+ r.push_tail 'logs', 'some log message'
12
+ r.push_tail 'logs', 'another log message'
13
+ r.push_tail 'logs', 'yet another log message'
14
+ r.push_tail 'logs', 'also another log message'
15
+
16
+ puts
17
+ p 'contents of logs LIST'
18
+
19
+ p r.list_range('logs', 0, -1)
20
+
21
+ puts
22
+ p 'Trim logs LIST to last 2 elements(easy circular buffer)'
23
+
24
+ r.list_trim('logs', -2, -1)
25
+
26
+ p r.list_range('logs', 0, -1)
@@ -0,0 +1,36 @@
1
+ require 'rubygems'
2
+ require 'redis'
3
+
4
+ r = Redis.new
5
+
6
+ r.delete 'foo-tags'
7
+ r.delete 'bar-tags'
8
+
9
+ puts
10
+ p "create a set of tags on foo-tags"
11
+
12
+ r.set_add 'foo-tags', 'one'
13
+ r.set_add 'foo-tags', 'two'
14
+ r.set_add 'foo-tags', 'three'
15
+
16
+ puts
17
+ p "create a set of tags on bar-tags"
18
+
19
+ r.set_add 'bar-tags', 'three'
20
+ r.set_add 'bar-tags', 'four'
21
+ r.set_add 'bar-tags', 'five'
22
+
23
+ puts
24
+ p 'foo-tags'
25
+
26
+ p r.set_members('foo-tags')
27
+
28
+ puts
29
+ p 'bar-tags'
30
+
31
+ p r.set_members('bar-tags')
32
+
33
+ puts
34
+ p 'intersection of foo-tags and bar-tags'
35
+
36
+ p r.set_intersect('foo-tags', 'bar-tags')
@@ -0,0 +1,124 @@
1
+ require 'redis'
2
+ require 'hash_ring'
3
+ class DistRedis
4
+ attr_reader :ring
5
+ def initialize(opts={})
6
+ hosts = []
7
+
8
+ db = opts[:db] || nil
9
+ timeout = opts[:timeout] || nil
10
+
11
+ raise Error, "No hosts given" unless opts[:hosts]
12
+
13
+ opts[:hosts].each do |h|
14
+ host, port = h.split(':')
15
+ hosts << Redis.new(:host => host, :port => port, :db => db, :timeout => timeout)
16
+ end
17
+
18
+ @ring = HashRing.new hosts
19
+ end
20
+
21
+ def node_for_key(key)
22
+ key = $1 if key =~ /\{(.*)?\}/
23
+ @ring.get_node(key)
24
+ end
25
+
26
+ def add_server(server)
27
+ server, port = server.split(':')
28
+ @ring.add_node Redis.new(:host => server, :port => port)
29
+ end
30
+
31
+ def method_missing(sym, *args, &blk)
32
+ if redis = node_for_key(args.first.to_s)
33
+ redis.send sym, *args, &blk
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ def keys(glob)
40
+ @ring.nodes.map do |red|
41
+ red.keys(glob)
42
+ end
43
+ end
44
+
45
+ def save
46
+ on_each_node :save
47
+ end
48
+
49
+ def bgsave
50
+ on_each_node :bgsave
51
+ end
52
+
53
+ def quit
54
+ on_each_node :quit
55
+ end
56
+
57
+ def flush_all
58
+ on_each_node :flush_all
59
+ end
60
+ alias_method :flushall, :flush_all
61
+
62
+ def flush_db
63
+ on_each_node :flush_db
64
+ end
65
+ alias_method :flushdb, :flush_db
66
+
67
+ def delete_cloud!
68
+ @ring.nodes.each do |red|
69
+ red.keys("*").each do |key|
70
+ red.delete key
71
+ end
72
+ end
73
+ end
74
+
75
+ def on_each_node(command, *args)
76
+ @ring.nodes.each do |red|
77
+ red.send(command, *args)
78
+ end
79
+ end
80
+
81
+ end
82
+
83
+
84
+ if __FILE__ == $0
85
+
86
+ r = DistRedis.new 'localhost:6379', 'localhost:6380', 'localhost:6381', 'localhost:6382'
87
+ r['urmom'] = 'urmom'
88
+ r['urdad'] = 'urdad'
89
+ r['urmom1'] = 'urmom1'
90
+ r['urdad1'] = 'urdad1'
91
+ r['urmom2'] = 'urmom2'
92
+ r['urdad2'] = 'urdad2'
93
+ r['urmom3'] = 'urmom3'
94
+ r['urdad3'] = 'urdad3'
95
+ p r['urmom']
96
+ p r['urdad']
97
+ p r['urmom1']
98
+ p r['urdad1']
99
+ p r['urmom2']
100
+ p r['urdad2']
101
+ p r['urmom3']
102
+ p r['urdad3']
103
+
104
+ r.push_tail 'listor', 'foo1'
105
+ r.push_tail 'listor', 'foo2'
106
+ r.push_tail 'listor', 'foo3'
107
+ r.push_tail 'listor', 'foo4'
108
+ r.push_tail 'listor', 'foo5'
109
+
110
+ p r.pop_tail('listor')
111
+ p r.pop_tail('listor')
112
+ p r.pop_tail('listor')
113
+ p r.pop_tail('listor')
114
+ p r.pop_tail('listor')
115
+
116
+ puts "key distribution:"
117
+
118
+ r.ring.nodes.each do |red|
119
+ p [red.port, red.keys("*")]
120
+ end
121
+ r.delete_cloud!
122
+ p r.keys('*')
123
+
124
+ end
@@ -0,0 +1,128 @@
1
+ require 'zlib'
2
+
3
+ class HashRing
4
+
5
+ POINTS_PER_SERVER = 160 # this is the default in libmemcached
6
+
7
+ attr_reader :ring, :sorted_keys, :replicas, :nodes
8
+
9
+ # nodes is a list of objects that have a proper to_s representation.
10
+ # replicas indicates how many virtual points should be used pr. node,
11
+ # replicas are required to improve the distribution.
12
+ def initialize(nodes=[], replicas=POINTS_PER_SERVER)
13
+ @replicas = replicas
14
+ @ring = {}
15
+ @nodes = []
16
+ @sorted_keys = []
17
+ nodes.each do |node|
18
+ add_node(node)
19
+ end
20
+ end
21
+
22
+ # Adds a `node` to the hash ring (including a number of replicas).
23
+ def add_node(node)
24
+ @nodes << node
25
+ @replicas.times do |i|
26
+ key = Zlib.crc32("#{node}:#{i}")
27
+ @ring[key] = node
28
+ @sorted_keys << key
29
+ end
30
+ @sorted_keys.sort!
31
+ end
32
+
33
+ def remove_node(node)
34
+ @nodes.reject!{|n| n.to_s == node.to_s}
35
+ @replicas.times do |i|
36
+ key = Zlib.crc32("#{node}:#{i}")
37
+ @ring.delete(key)
38
+ @sorted_keys.reject! {|k| k == key}
39
+ end
40
+ end
41
+
42
+ # get the node in the hash ring for this key
43
+ def get_node(key)
44
+ get_node_pos(key)[0]
45
+ end
46
+
47
+ def get_node_pos(key)
48
+ return [nil,nil] if @ring.size == 0
49
+ crc = Zlib.crc32(key)
50
+ idx = HashRing.binary_search(@sorted_keys, crc)
51
+ return [@ring[@sorted_keys[idx]], idx]
52
+ end
53
+
54
+ def iter_nodes(key)
55
+ return [nil,nil] if @ring.size == 0
56
+ node, pos = get_node_pos(key)
57
+ @sorted_keys[pos..-1].each do |k|
58
+ yield @ring[k]
59
+ end
60
+ end
61
+
62
+ class << self
63
+
64
+ # gem install RubyInline to use this code
65
+ # Native extension to perform the binary search within the hashring.
66
+ # There's a pure ruby version below so this is purely optional
67
+ # for performance. In testing 20k gets and sets, the native
68
+ # binary search shaved about 12% off the runtime (9sec -> 8sec).
69
+ begin
70
+ require 'inline'
71
+ inline do |builder|
72
+ builder.c <<-EOM
73
+ int binary_search(VALUE ary, unsigned int r) {
74
+ int upper = RARRAY_LEN(ary) - 1;
75
+ int lower = 0;
76
+ int idx = 0;
77
+
78
+ while (lower <= upper) {
79
+ idx = (lower + upper) / 2;
80
+
81
+ VALUE continuumValue = RARRAY_PTR(ary)[idx];
82
+ unsigned int l = NUM2UINT(continuumValue);
83
+ if (l == r) {
84
+ return idx;
85
+ }
86
+ else if (l > r) {
87
+ upper = idx - 1;
88
+ }
89
+ else {
90
+ lower = idx + 1;
91
+ }
92
+ }
93
+ return upper;
94
+ }
95
+ EOM
96
+ end
97
+ rescue Exception => e
98
+ # Find the closest index in HashRing with value <= the given value
99
+ def binary_search(ary, value, &block)
100
+ upper = ary.size - 1
101
+ lower = 0
102
+ idx = 0
103
+
104
+ while(lower <= upper) do
105
+ idx = (lower + upper) / 2
106
+ comp = ary[idx] <=> value
107
+
108
+ if comp == 0
109
+ return idx
110
+ elsif comp > 0
111
+ upper = idx - 1
112
+ else
113
+ lower = idx + 1
114
+ end
115
+ end
116
+ return upper
117
+ end
118
+
119
+ end
120
+ end
121
+
122
+ end
123
+
124
+ # ring = HashRing.new ['server1', 'server2', 'server3']
125
+ # p ring
126
+ # #
127
+ # p ring.get_node "kjhjkjlkjlkkh"
128
+ #