vanity 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/CHANGELOG +35 -0
  2. data/README.rdoc +33 -6
  3. data/lib/vanity.rb +13 -7
  4. data/lib/vanity/backport.rb +43 -0
  5. data/lib/vanity/commands/report.rb +13 -3
  6. data/lib/vanity/experiment/ab_test.rb +98 -66
  7. data/lib/vanity/experiment/base.rb +51 -5
  8. data/lib/vanity/metric.rb +213 -0
  9. data/lib/vanity/mock_redis.rb +76 -0
  10. data/lib/vanity/playground.rb +78 -61
  11. data/lib/vanity/rails/dashboard.rb +11 -2
  12. data/lib/vanity/rails/helpers.rb +3 -3
  13. data/lib/vanity/templates/_ab_test.erb +3 -4
  14. data/lib/vanity/templates/_experiment.erb +4 -4
  15. data/lib/vanity/templates/_experiments.erb +2 -2
  16. data/lib/vanity/templates/_metric.erb +9 -0
  17. data/lib/vanity/templates/_metrics.erb +13 -0
  18. data/lib/vanity/templates/_report.erb +14 -3
  19. data/lib/vanity/templates/flot.min.js +1 -0
  20. data/lib/vanity/templates/jquery.min.js +19 -0
  21. data/lib/vanity/templates/vanity.css +16 -4
  22. data/lib/vanity/templates/vanity.js +96 -0
  23. data/test/ab_test_test.rb +159 -96
  24. data/test/experiment_test.rb +99 -18
  25. data/test/experiments/age_and_zipcode.rb +1 -0
  26. data/test/experiments/metrics/cheers.rb +3 -0
  27. data/test/experiments/metrics/signups.rb +2 -0
  28. data/test/experiments/metrics/yawns.rb +3 -0
  29. data/test/experiments/null_abc.rb +1 -0
  30. data/test/metric_test.rb +287 -0
  31. data/test/playground_test.rb +1 -80
  32. data/test/rails_test.rb +9 -6
  33. data/test/test_helper.rb +37 -6
  34. data/vanity.gemspec +1 -1
  35. data/vendor/{redis-0.1 → redis-rb}/LICENSE +0 -0
  36. data/vendor/{redis-0.1 → redis-rb}/README.markdown +0 -0
  37. data/vendor/{redis-0.1 → redis-rb}/Rakefile +0 -0
  38. data/vendor/redis-rb/bench.rb +44 -0
  39. data/vendor/redis-rb/benchmarking/suite.rb +24 -0
  40. data/vendor/redis-rb/benchmarking/worker.rb +71 -0
  41. data/vendor/redis-rb/bin/distredis +33 -0
  42. data/vendor/redis-rb/examples/basic.rb +16 -0
  43. data/vendor/redis-rb/examples/incr-decr.rb +18 -0
  44. data/vendor/redis-rb/examples/list.rb +26 -0
  45. data/vendor/redis-rb/examples/sets.rb +36 -0
  46. data/vendor/{redis-0.1 → redis-rb}/lib/dist_redis.rb +0 -0
  47. data/vendor/{redis-0.1 → redis-rb}/lib/hash_ring.rb +0 -0
  48. data/vendor/{redis-0.1 → redis-rb}/lib/pipeline.rb +0 -2
  49. data/vendor/{redis-0.1 → redis-rb}/lib/redis.rb +25 -7
  50. data/vendor/{redis-0.1 → redis-rb}/lib/redis/raketasks.rb +0 -0
  51. data/vendor/redis-rb/profile.rb +22 -0
  52. data/vendor/redis-rb/redis-rb.gemspec +30 -0
  53. data/vendor/{redis-0.1 → redis-rb}/spec/redis_spec.rb +113 -0
  54. data/vendor/{redis-0.1 → redis-rb}/spec/spec_helper.rb +0 -0
  55. data/vendor/redis-rb/speed.rb +16 -0
  56. data/vendor/{redis-0.1 → redis-rb}/tasks/redis.tasks.rb +5 -1
  57. metadata +37 -14
@@ -1,89 +1,10 @@
1
1
  require "test/test_helper"
2
2
 
3
- class PlaygroundTest < MiniTest::Unit::TestCase
4
- def setup
5
- @namespace = "vanity:0"
6
- end
3
+ class PlaygroundTest < Test::Unit::TestCase
7
4
 
8
5
  def test_has_one_global_instance
9
6
  assert instance = Vanity.playground
10
7
  assert_equal instance, Vanity.playground
11
8
  end
12
9
 
13
- def test_playground_namespace
14
- assert @namespace, Vanity.playground.namespace
15
- end
16
-
17
-
18
- # -- Loading experiments --
19
-
20
- def test_fails_if_cannot_load_named_experiment
21
- assert_raises LoadError do
22
- experiment("Green button")
23
- end
24
- end
25
-
26
- def test_loading_experiment
27
- Vanity.playground.load_path = Dir.tmpdir
28
- File.open File.join(Dir.tmpdir, "green_button.rb"), "w" do |f|
29
- f.write <<-RUBY
30
- ab_test "Green Button" do
31
- def xmts
32
- "x"
33
- end
34
- end
35
- RUBY
36
- end
37
- assert_equal "x", experiment(:green_button).xmts
38
- end
39
-
40
- def test_fails_if_error_loading_experiment
41
- Vanity.playground.load_path = Dir.tmpdir
42
- File.open File.join(Dir.tmpdir, "green_button.rb"), "w" do |f|
43
- f.write "fail 'yawn!'"
44
- end
45
- assert_raises LoadError do
46
- experiment(:green_button)
47
- end
48
- end
49
-
50
- def test_complains_if_not_defined_where_expected
51
- Vanity.playground.load_path = Dir.tmpdir
52
- File.open File.join(Dir.tmpdir, "green_button.rb"), "w" do |f|
53
- f.write ""
54
- end
55
- assert_raises LoadError do
56
- experiment("Green button")
57
- end
58
- end
59
-
60
- def test_reloading_experiments
61
- Vanity.playground.define(:ab, :ab_test) {}
62
- Vanity.playground.define(:cd, :ab_test) {}
63
- assert 2, Vanity.playground.experiments.count
64
- Vanity.playground.reload!
65
- assert_empty Vanity.playground.experiments
66
- end
67
-
68
- # -- Defining experiment --
69
-
70
- def test_can_access_experiment_by_name_or_id
71
- exp = Vanity.playground.define(:green_button, :ab_test) { }
72
- assert_equal exp, experiment("Green Button")
73
- assert_equal exp, experiment(:green_button)
74
- end
75
-
76
- def test_fail_when_defining_same_experiment_twice
77
- Vanity.playground.define("Green Button", :ab_test) { }
78
- assert_raises RuntimeError do
79
- Vanity.playground.define("Green Button", :ab_test) { }
80
- end
81
- end
82
-
83
- def test_uses_playground_namespace_for_experiment
84
- Vanity.playground.define(:green_button, :ab_test) { }
85
- assert_equal "#{@namespace}:green_button", experiment(:green_button).send(:key)
86
- assert_equal "#{@namespace}:green_button:participants", experiment(:green_button).send(:key, "participants")
87
- end
88
-
89
10
  end
data/test/rails_test.rb CHANGED
@@ -4,7 +4,7 @@ class UseVanityController < ActionController::Base
4
4
  attr_accessor :current_user
5
5
 
6
6
  def index
7
- render text: ab_test(:simple)
7
+ render :text=>ab_test(:pie_or_cake)
8
8
  end
9
9
  end
10
10
 
@@ -13,7 +13,10 @@ class UseVanityTest < ActionController::TestCase
13
13
  tests UseVanityController
14
14
 
15
15
  def setup
16
- Vanity.playground.define :simple, :ab_test do
16
+ super
17
+ metric :sugar_high
18
+ Vanity.playground.define :pie_or_cake, :ab_test do
19
+ metrics :sugar_high
17
20
  end
18
21
  UseVanityController.class_eval do
19
22
  use_vanity :current_user
@@ -29,7 +32,7 @@ class UseVanityTest < ActionController::TestCase
29
32
 
30
33
  def test_vanity_cookie_default_id
31
34
  get :index
32
- assert_match cookies['vanity_id'], /^[a-f0-9]{32}$/
35
+ assert cookies['vanity_id'] =~ /^[a-f0-9]{32}$/
33
36
  end
34
37
 
35
38
  def test_vanity_cookie_retains_id
@@ -45,7 +48,7 @@ class UseVanityTest < ActionController::TestCase
45
48
  end
46
49
 
47
50
  def test_vanity_identity_set_from_user
48
- @controller.current_user = mock("user", id: "user_id")
51
+ @controller.current_user = mock("user", :id=>"user_id")
49
52
  get :index
50
53
  assert_equal "user_id", @controller.send(:vanity_identity)
51
54
  end
@@ -56,7 +59,7 @@ class UseVanityTest < ActionController::TestCase
56
59
  end
57
60
  @controller.current_user = Object.new
58
61
  get :index
59
- assert_match cookies['vanity_id'], /^[a-f0-9]{32}$/
62
+ assert cookies['vanity_id'] =~ /^[a-f0-9]{32}$/
60
63
  end
61
64
 
62
65
  def test_vanity_identity_set_with_block
@@ -70,7 +73,7 @@ class UseVanityTest < ActionController::TestCase
70
73
  end
71
74
 
72
75
  def teardown
76
+ super
73
77
  UseVanityController.send(:filter_chain).clear
74
- nuke_playground
75
78
  end
76
79
  end
data/test/test_helper.rb CHANGED
@@ -1,35 +1,66 @@
1
1
  $LOAD_PATH.delete_if { |path| path[/gems\/vanity-\d/] }
2
2
  $LOAD_PATH.unshift File.expand_path("../lib", File.dirname(__FILE__))
3
3
  RAILS_ROOT = File.expand_path("..")
4
- require "minitest/spec"
4
+ require "test/unit"
5
5
  require "mocha"
6
6
  require "action_controller"
7
7
  require "action_controller/test_case"
8
8
  require "initializer"
9
9
  require "lib/vanity/rails"
10
- MiniTest::Unit.autorun
10
+ require "timecop"
11
+
12
+
13
+ class Test::Unit::TestCase
14
+
15
+ def setup
16
+ FileUtils.mkpath "tmp/experiments/metrics"
17
+ new_playground
18
+ end
11
19
 
12
- class MiniTest::Unit::TestCase
13
20
  # Call this on teardown. It wipes put the playground and any state held in it
14
21
  # (mostly experiments), resets vanity ID, and clears Redis of all experiments.
15
22
  def nuke_playground
16
- Vanity.playground.redis.flushdb
17
23
  new_playground
24
+ Vanity.playground.redis.flushdb
18
25
  end
19
26
 
20
27
  # Call this if you need a new playground, e.g. to re-define the same experiment,
21
28
  # or reload an experiment (saved by the previous playground).
22
29
  def new_playground
23
- Vanity.instance_variable_set :@playground, Vanity::Playground.new
30
+ logger = Logger.new("/dev/null") unless $VERBOSE
31
+ Vanity.playground = Vanity::Playground.new(:logger=>logger, :load_path=>"tmp/experiments", :db=>15)
32
+ Vanity.playground.mock! unless ENV["REDIS"]
24
33
  end
25
34
 
35
+ # Defines the specified metrics (one or more names). Returns metric, or array
36
+ # of metric (if more than one argument).
37
+ def metric(*names)
38
+ metrics = names.map do |name|
39
+ id = name.to_s.downcase.gsub(/\W+/, '_').to_sym
40
+ Vanity.playground.metrics[id] ||= Vanity::Metric.new(Vanity.playground, name)
41
+ end
42
+ names.size == 1 ? metrics.first : metrics
43
+ end
44
+
26
45
  def teardown
27
- nuke_playground
28
46
  Vanity.context = nil
47
+ FileUtils.rm_rf "tmp"
48
+ Vanity.playground.redis.flushdb
29
49
  end
50
+
30
51
  end
31
52
 
32
53
  ActionController::Routing::Routes.draw do |map|
33
54
  map.connect ':controller/:action/:id'
34
55
  end
35
56
  Rails.configuration = Rails::Configuration.new
57
+
58
+ class Array
59
+ # Not in Ruby 1.8.6.
60
+ unless method_defined?(:shuffle)
61
+ def shuffle
62
+ copy = clone
63
+ Array.new(size) { copy.delete_at(Kernel.rand(copy.size)) }
64
+ end
65
+ end
66
+ end
data/vanity.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "vanity"
3
- spec.version = "1.0.0"
3
+ spec.version = "1.1.0"
4
4
  spec.author = "Assaf Arkin"
5
5
  spec.email = "assaf@labnotes.org"
6
6
  spec.homepage = "http://vanity.labnotes.org"
File without changes
File without changes
File without changes
@@ -0,0 +1,44 @@
1
+ require 'benchmark'
2
+ $:.push File.join(File.dirname(__FILE__), 'lib')
3
+ require 'redis'
4
+
5
+ times = 20000
6
+
7
+ @r = Redis.new#(:debug => true)
8
+ @r['foo'] = "The first line we sent to the server is some text"
9
+
10
+ Benchmark.bmbm do |x|
11
+ x.report("set") do
12
+ 20000.times do |i|
13
+ @r["set#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
14
+ end
15
+ end
16
+
17
+ x.report("set (pipelined)") do
18
+ @r.pipelined do |pipeline|
19
+ 20000.times do |i|
20
+ pipeline["set_pipelined#{i}"] = "The first line we sent to the server is some text"; @r["foo#{i}"]
21
+ end
22
+ end
23
+ end
24
+
25
+ x.report("push+trim") do
26
+ 20000.times do |i|
27
+ @r.push_head "push_trim#{i}", i
28
+ @r.list_trim "push_trim#{i}", 0, 30
29
+ end
30
+ end
31
+
32
+ x.report("push+trim (pipelined)") do
33
+ @r.pipelined do |pipeline|
34
+ 20000.times do |i|
35
+ pipeline.push_head "push_trim_pipelined#{i}", i
36
+ pipeline.list_trim "push_trim_pipelined#{i}", 0, 30
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ @r.keys('*').each do |k|
43
+ @r.delete k
44
+ end
@@ -0,0 +1,24 @@
1
+ require 'fileutils'
2
+
3
+ def run_in_background(command)
4
+ fork { system command }
5
+ end
6
+
7
+ def with_all_segments(&block)
8
+ 0.upto(9) do |segment_number|
9
+ block_size = 100000
10
+ start_index = segment_number * block_size
11
+ end_index = start_index + block_size - 1
12
+ block.call(start_index, end_index)
13
+ end
14
+ end
15
+
16
+ #with_all_segments do |start_index, end_index|
17
+ # puts "Initializing keys from #{start_index} to #{end_index}"
18
+ # system "ruby worker.rb initialize #{start_index} #{end_index} 0"
19
+ #end
20
+
21
+ with_all_segments do |start_index, end_index|
22
+ run_in_background "ruby worker.rb write #{start_index} #{end_index} 10"
23
+ run_in_background "ruby worker.rb read #{start_index} #{end_index} 1"
24
+ end
@@ -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')