diff --git a/Appraisals b/Appraisals index a67b688b..92690716 100644 --- a/Appraisals +++ b/Appraisals @@ -1,19 +1,22 @@ appraise "3.0" do gem "rails", "~> 3.0.20" - gem "split", :path => "../" + gem "connection_pool" end appraise "3.1" do gem "rails", "~> 3.1.12" + gem "connection_pool" gem "split", :path => "../" end appraise "3.2" do gem "rails", "~> 3.2.13" + gem "connection_pool" gem "split", :path => "../" end appraise "4.0" do gem "rails", "4.0.0.rc1" + gem "connection_pool" gem "split", :path => "../" end \ No newline at end of file diff --git a/gemfiles/3.0.gemfile b/gemfiles/3.0.gemfile index b79d01a8..e138155c 100644 --- a/gemfiles/3.0.gemfile +++ b/gemfiles/3.0.gemfile @@ -4,6 +4,6 @@ source "https://rubygems.org" gem "appraisal" gem "rails", "~> 3.0.20" -gem "split", :path=>"../" +gem "connection_pool" -gemspec :path=>"../" \ No newline at end of file +gemspec :path => "../" diff --git a/gemfiles/3.0.gemfile.lock b/gemfiles/3.0.gemfile.lock index c067da73..09111ad8 100644 --- a/gemfiles/3.0.gemfile.lock +++ b/gemfiles/3.0.gemfile.lock @@ -1,7 +1,8 @@ PATH - remote: /Users/andrew/code/split + remote: ../ specs: - split (0.6.1) + split (0.7.2) + connection_pool redis (>= 2.1) redis-namespace (>= 1.1.0) simple-random @@ -41,10 +42,23 @@ GEM bundler rake arel (2.0.10) + backports (3.6.6) builder (2.1.2) - diff-lcs (1.2.2) + connection_pool (2.2.0) + coveralls (0.7.1) + multi_json (~> 1.3) + rest-client + simplecov (>= 0.7) + term-ansicolor + thor + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.24) + unf (>= 0.0.5, < 1.0.0) erubis (2.6.6) abstract (>= 1.0.0) + http-cookie (1.0.2) + domain_name (~> 0.5) i18n (0.5.0) json (1.7.7) mail (2.2.19) @@ -53,6 +67,8 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) mime-types (1.23) + multi_json (1.11.2) + netrc (0.10.3) polyglot (0.3.3) rack (1.2.7) rack-mount (0.6.14) @@ -76,27 +92,43 @@ GEM rake (10.0.4) rdoc (3.12.2) json (~> 1.4) - redis (3.0.3) - redis-namespace (1.2.1) - redis (~> 3.0.0) - rspec (2.13.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) - rspec-core (2.13.1) - rspec-expectations (2.13.0) + redis (3.2.1) + redis-namespace (1.5.2) + redis (~> 3.0, >= 3.0.4) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-core (2.99.2) + rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.13.0) - simple-random (0.9.3) - sinatra (1.2.8) - rack (~> 1.1) + rspec-mocks (2.99.4) + simple-random (1.0.1) + simplecov (0.9.2) + docile (~> 1.1.0) + multi_json (~> 1.0) + simplecov-html (~> 0.9.0) + simplecov-html (0.9.0) + sinatra (1.2.9) + backports + rack (~> 1.1, < 1.5) tilt (>= 1.2.2, < 2.0) + term-ansicolor (1.3.2) + tins (~> 1.0) thor (0.14.6) - tilt (1.3.7) + tilt (1.4.1) + tins (1.6.0) treetop (1.4.12) polyglot polyglot (>= 0.3.1) tzinfo (0.3.37) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.1) PLATFORMS ruby @@ -104,8 +136,10 @@ PLATFORMS DEPENDENCIES appraisal bundler (~> 1.3) + connection_pool + coveralls rack-test (>= 0.5.7) rails (~> 3.0.20) rake - rspec (~> 2.12) + rspec (~> 2.14) split! diff --git a/gemfiles/3.1.gemfile b/gemfiles/3.1.gemfile index 6fcbecfe..86fb2f04 100644 --- a/gemfiles/3.1.gemfile +++ b/gemfiles/3.1.gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "appraisal" gem "rails", "~> 3.1.12" -gem "split", :path=>"../" +gem "split", :path => "../../" +gem "connection_pool" -gemspec :path=>"../" \ No newline at end of file +gemspec :path => "../" diff --git a/gemfiles/3.2.gemfile b/gemfiles/3.2.gemfile index 54efc9da..6c7c20ad 100644 --- a/gemfiles/3.2.gemfile +++ b/gemfiles/3.2.gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "appraisal" gem "rails", "~> 3.2.13" -gem "split", :path=>"../" +gem "split", :path => "../../" +gem "connection_pool" -gemspec :path=>"../" \ No newline at end of file +gemspec :path => "../" diff --git a/gemfiles/4.0.gemfile b/gemfiles/4.0.gemfile index f901bbd9..f0c5d2c9 100644 --- a/gemfiles/4.0.gemfile +++ b/gemfiles/4.0.gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "appraisal" gem "rails", "4.0.0.rc1" -gem "split", :path=>"../" +gem "split", :path => "../../" +gem "connection_pool" -gemspec :path=>"../" \ No newline at end of file +gemspec :path => "../" diff --git a/lib/split.rb b/lib/split.rb index bdf49c32..4b6b487c 100755 --- a/lib/split.rb +++ b/lib/split.rb @@ -33,6 +33,7 @@ module Split # 5. An instance of `Redis`, `Redis::Client`, `Redis::DistRedis`, # or `Redis::Namespace`. def redis=(server) + if server.respond_to? :split if server["redis://"] namespace ||= :split diff --git a/lib/split/alternative.rb b/lib/split/alternative.rb index f2766028..a471475e 100644 --- a/lib/split/alternative.rb +++ b/lib/split/alternative.rb @@ -1,5 +1,6 @@ require 'split/zscore' require 'active_support/all' +require 'simple-random' # TODO - take out require and implement using file paths? module Split @@ -175,9 +176,6 @@ def increment_completion(goal = nil, value = nil) def increment_unique_completion(goal = nil, value = nil) Split.redis.with do |conn| field = set_field(goal, true) - if value - conn.lpush(key + set_value_field(goal, true), value) - end conn.hincrby(key, field, 1) end end @@ -216,7 +214,7 @@ def z_score(goal = nil) n_a = alternative.participant_count n_c = control.participant_count - + z_score = Split::Zscore.calculate(p_a, n_a, p_c, n_c) end @@ -467,6 +465,35 @@ def reset def delete Split.redis.with do |conn| conn.del(key) + unless goals.empty? + goals.each do |g| + field = "completed_count:#{g}" + value_field = set_value_field(g) + conn.del(key + value_field) + conn.del(key + field) + + field = "unique_completed_count:#{g}" + value_field = set_value_field(g, true) + conn.del(key + value_field) + conn.del(key + field) + end + end + end + end + + def flatten_values + Split.redis.with do |conn| + unless goals.empty? + goals.each do |g| + value_field = set_value_field(g) + avg = completed_value(g) + conn.del(key + value_field) + conn.lpush(key + value_field, avg) + + value_field = set_value_field(g, true) + conn.del(key + value_field) + end + end end end diff --git a/lib/split/configuration.rb b/lib/split/configuration.rb index 507bd91a..d07742f1 100644 --- a/lib/split/configuration.rb +++ b/lib/split/configuration.rb @@ -17,6 +17,7 @@ class Configuration attr_accessor :on_experiment_delete attr_accessor :on_experiment_max_out attr_accessor :on_experiment_end + attr_accessor :on_garbage_collection attr_accessor :include_rails_helper attr_accessor :pipeline_size @@ -204,6 +205,7 @@ def initialize @on_experiment_max_out = proc{|experiment|} @on_experiment_delete = proc{|experiment|} @on_experiment_end = proc{|experiment|} + @on_garbage_collection = proc{|experiment|} @db_failover_allow_parameter_override = false @enabled = true @experiments = {} diff --git a/lib/split/dashboard/helpers.rb b/lib/split/dashboard/helpers.rb index 1153207d..40b33a15 100644 --- a/lib/split/dashboard/helpers.rb +++ b/lib/split/dashboard/helpers.rb @@ -17,7 +17,9 @@ def number_to_currency(number) end def round(number, precision = 2) - BigDecimal.new(number.to_s).round(precision).to_f + BigDecimal(number.to_s).round(precision).to_f + rescue ArgumentError + number.to_f end def confidence_level(z_score) diff --git a/lib/split/experiment.rb b/lib/split/experiment.rb index 066fc4b7..80287e82 100644 --- a/lib/split/experiment.rb +++ b/lib/split/experiment.rb @@ -217,6 +217,7 @@ def winner=(winner_name) set_end_time delete_participants delete_finished + alternatives.each(&:flatten_values) end def participant_count @@ -505,20 +506,17 @@ def delete def delete_participants Split.redis.with do |conn| - goals.each do |goal| - conn.del("#{self.key}:participants") - end + Split::Persistence.adapter.garbage_collect("sets", "#{self.key}:participants") end end def delete_finished Split.redis.with do |conn| key = "#{self.key}:finished" - conn.del(key) + Split::Persistence.adapter.garbage_collect("sets", key) (goals).each do |goal| - conn.del("#{key}:#{goal}") + Split::Persistence.adapter.garbage_collect("sets", "#{key}:#{goal}") end - conn.del("#{key}") end end diff --git a/lib/split/helper.rb b/lib/split/helper.rb index 9dab2e39..66780ee4 100644 --- a/lib/split/helper.rb +++ b/lib/split/helper.rb @@ -173,10 +173,6 @@ def ab_user @ab_user ||= Split::Persistence.adapter.new(self) end - def split_id - ab_user.key_frag - end - def exclude_visitor? instance_eval(&Split.configuration.ignore_filter) end @@ -196,6 +192,10 @@ def is_ignored_ip_address? protected + def split_id + ab_user.key_frag + end + def normalize_experiment(metric_descriptor) if Hash === metric_descriptor experiment_name = metric_descriptor.keys.first diff --git a/lib/split/persistence/redis_adapter.rb b/lib/split/persistence/redis_adapter.rb index fc15a7d3..8c530f8d 100644 --- a/lib/split/persistence/redis_adapter.rb +++ b/lib/split/persistence/redis_adapter.rb @@ -81,6 +81,18 @@ def self.fetch_values_in_batch(split_ids, field) def self.get_redis_key(key_frag) "#{config[:namespace]}:#{key_frag}" end + + def self.garbage_collect(type, key) + Split.redis.with do |conn| + begin + new_key = "gc:#{type}:#{conn.incr("gc:index")}" + conn.rename(key, new_key) + Split.configuration.on_garbage_collection.call(new_key) + rescue Redis::CommandError => e + raise e unless e.message.include?("no such key") + end + end + end def get_redis_key(key_frag) self.class.get_redis_key(key_frag)