vanity 1.0.0 → 1.1.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 (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')