1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
# frozen_string_literal: true
require "zlib"
# The percent rollout strategy is the most comprehensive included with Gitlab::Experiment. It allows specifying the
# percentages per variant using an array, a hash, or will default to even distribution when no rules are provided.
#
# A given experiment id (context key) will always be given the same variant assignment.
#
# Example configuration usage:
#
# config.default_rollout = Gitlab::Experiment::Rollout::Percent.new
#
# Example class usage:
#
# class PillColorExperiment < ApplicationExperiment
# control { }
# variant(:red) { }
# variant(:blue) { }
#
# # Even distribution between all behaviors.
# default_rollout :percent
#
# # With specific distribution percentages.
# default_rollout :percent, distribution: { control: 25, red: 30, blue: 45 }
# end
#
module Gitlab
class Experiment
module Rollout
class Percent < Base
protected
def validate!
case distribution_rules
when nil then nil
when Array
validate_distribution_rules(distribution_rules)
when Hash
validate_distribution_rules(distribution_rules.values)
else
raise InvalidRolloutRules, 'unknown distribution options type'
end
end
def execute_assignment
crc = normalized_id
total = 0
case distribution_rules
when Array # run through the rules until finding an acceptable one
behavior_names[distribution_rules.find_index { |percent| crc % 100 <= total += percent }]
when Hash # run through the variant names until finding an acceptable one
distribution_rules.find { |_, percent| crc % 100 <= total += percent }.first
else # assume even distribution on no rules
behavior_names.empty? ? nil : behavior_names[crc % behavior_names.length]
end
end
private
def normalized_id
Zlib.crc32(id, nil)
end
def distribution_rules
options[:distribution]
end
def validate_distribution_rules(distributions)
if distributions.length != behavior_names.length
raise InvalidRolloutRules, "the distribution rules don't match the number of behaviors defined"
end
return if distributions.sum == 100
raise InvalidRolloutRules, 'the distribution percentages should add up to 100'
end
end
end
end
end
|