File: percent.rb

package info (click to toggle)
ruby-gitlab-experiment 0.9.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 260 kB
  • sloc: ruby: 1,202; makefile: 7
file content (82 lines) | stat: -rw-r--r-- 2,501 bytes parent folder | download
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