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.
- data/CHANGELOG +153 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +83 -0
- data/bin/vanity +53 -0
- data/lib/vanity.rb +38 -0
- data/lib/vanity/backport.rb +43 -0
- data/lib/vanity/commands.rb +2 -0
- data/lib/vanity/commands/list.rb +21 -0
- data/lib/vanity/commands/report.rb +60 -0
- data/lib/vanity/experiment/ab_test.rb +477 -0
- data/lib/vanity/experiment/base.rb +212 -0
- data/lib/vanity/helpers.rb +59 -0
- data/lib/vanity/metric/active_record.rb +77 -0
- data/lib/vanity/metric/base.rb +221 -0
- data/lib/vanity/metric/google_analytics.rb +70 -0
- data/lib/vanity/mock_redis.rb +76 -0
- data/lib/vanity/playground.rb +197 -0
- data/lib/vanity/rails.rb +22 -0
- data/lib/vanity/rails/dashboard.rb +24 -0
- data/lib/vanity/rails/helpers.rb +158 -0
- data/lib/vanity/rails/testing.rb +11 -0
- data/lib/vanity/templates/_ab_test.erb +26 -0
- data/lib/vanity/templates/_experiment.erb +5 -0
- data/lib/vanity/templates/_experiments.erb +7 -0
- data/lib/vanity/templates/_metric.erb +14 -0
- data/lib/vanity/templates/_metrics.erb +13 -0
- data/lib/vanity/templates/_report.erb +27 -0
- data/lib/vanity/templates/flot.min.js +1 -0
- data/lib/vanity/templates/jquery.min.js +19 -0
- data/lib/vanity/templates/vanity.css +26 -0
- data/lib/vanity/templates/vanity.js +82 -0
- data/test/ab_test_test.rb +656 -0
- data/test/experiment_test.rb +136 -0
- data/test/experiments/age_and_zipcode.rb +19 -0
- data/test/experiments/metrics/cheers.rb +3 -0
- data/test/experiments/metrics/signups.rb +2 -0
- data/test/experiments/metrics/yawns.rb +3 -0
- data/test/experiments/null_abc.rb +5 -0
- data/test/metric_test.rb +518 -0
- data/test/playground_test.rb +10 -0
- data/test/rails_test.rb +104 -0
- data/test/test_helper.rb +135 -0
- data/vanity.gemspec +18 -0
- data/vendor/redis-rb/LICENSE +20 -0
- data/vendor/redis-rb/README.markdown +36 -0
- data/vendor/redis-rb/Rakefile +62 -0
- data/vendor/redis-rb/bench.rb +44 -0
- data/vendor/redis-rb/benchmarking/suite.rb +24 -0
- data/vendor/redis-rb/benchmarking/worker.rb +71 -0
- data/vendor/redis-rb/bin/distredis +33 -0
- data/vendor/redis-rb/examples/basic.rb +16 -0
- data/vendor/redis-rb/examples/incr-decr.rb +18 -0
- data/vendor/redis-rb/examples/list.rb +26 -0
- data/vendor/redis-rb/examples/sets.rb +36 -0
- data/vendor/redis-rb/lib/dist_redis.rb +124 -0
- data/vendor/redis-rb/lib/hash_ring.rb +128 -0
- data/vendor/redis-rb/lib/pipeline.rb +21 -0
- data/vendor/redis-rb/lib/redis.rb +370 -0
- data/vendor/redis-rb/lib/redis/raketasks.rb +1 -0
- data/vendor/redis-rb/profile.rb +22 -0
- data/vendor/redis-rb/redis-rb.gemspec +30 -0
- data/vendor/redis-rb/spec/redis_spec.rb +637 -0
- data/vendor/redis-rb/spec/spec_helper.rb +4 -0
- data/vendor/redis-rb/speed.rb +16 -0
- data/vendor/redis-rb/tasks/redis.tasks.rb +140 -0
- 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,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
|
+
#
|