statsig 1.30.0 → 1.32.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72119a11268473774b98457f89017155813cdaab4c19b8f07e1aad370ae6dd17
4
- data.tar.gz: 69cb96c04c6b9c322bb239332b8c406a6b543bb42b0cab6da9e641d44fa15371
3
+ metadata.gz: dd71ed81ad0f3658f5b6b480adba4a36f560357c67d6d9869b605e2d0e1e76c0
4
+ data.tar.gz: 59b7da08c4991da0e7b33dc08865612347442970932e71b8069bcd4518d50680
5
5
  SHA512:
6
- metadata.gz: d75c0a0d9c6529843c554d05b3ae49e6d863cd01da11f932e679bddddffab4287ef036af37c3cd8fff9310c10cd6758bc6e5435f78626245c5d1f315b91e09eb
7
- data.tar.gz: b9886d0b78d1491393cfacdea4c1f4cdeae1d2a993a0f5a0875011be1083bb63e4a7721fb3a007869eaa7eaf69ad300438d8038c099e88277958d4daf6d226f7
6
+ metadata.gz: 3775510747a856d9ae300ccad20a74de3cbe000bc3bca59c535c64cbab1bcb46a83069aa917299ee6dd7032f44206eecd39f3602c92f18c39a8ebbb57b45d9e9
7
+ data.tar.gz: e0a03db37029a260614a6eb8bf9edf1e2fe60f84d67fb9e027bf5fd72c3c4f969a7ab3145f55d0bd19f796b7d1733416547688e1076c69e066abd819c99b899c
data/lib/api_config.rb ADDED
@@ -0,0 +1,128 @@
1
+ require 'constants'
2
+
3
+ class UnsupportedConfigException < StandardError
4
+ end
5
+
6
+ module Statsig
7
+ class APIConfig
8
+ attr_accessor :name, :type, :is_active, :salt, :default_value, :enabled,
9
+ :rules, :id_type, :entity, :explicit_parameters, :has_shared_params, :target_app_ids
10
+
11
+ def initialize(name:, type:, is_active:, salt:, default_value:, enabled:, rules:, id_type:, entity:,
12
+ explicit_parameters: nil, has_shared_params: nil, target_app_ids: nil)
13
+ @name = name
14
+ @type = type.to_sym unless entity.nil?
15
+ @is_active = is_active
16
+ @salt = salt
17
+ @default_value = JSON.parse(JSON.generate(default_value))
18
+ @enabled = enabled
19
+ @rules = rules
20
+ @id_type = id_type
21
+ @entity = entity.to_sym unless entity.nil?
22
+ @explicit_parameters = explicit_parameters
23
+ @has_shared_params = has_shared_params
24
+ @target_app_ids = target_app_ids
25
+ end
26
+
27
+ def self.from_json(json)
28
+ new(
29
+ name: json[:name],
30
+ type: json[:type],
31
+ is_active: json[:isActive],
32
+ salt: json[:salt],
33
+ default_value: json[:defaultValue] || {},
34
+ enabled: json[:enabled],
35
+ rules: json[:rules]&.map do |rule|
36
+ APIRule.from_json(rule)
37
+ end,
38
+ id_type: json[:idType],
39
+ entity: json[:entity],
40
+ explicit_parameters: json[:explicitParameters],
41
+ has_shared_params: json[:hasSharedParams],
42
+ target_app_ids: json[:targetAppIDs]
43
+ )
44
+ end
45
+ end
46
+ end
47
+
48
+ module Statsig
49
+ class APIRule
50
+
51
+ attr_accessor :name, :pass_percentage, :return_value, :id, :salt,
52
+ :conditions, :id_type, :group_name, :config_delegate, :is_experiment_group
53
+
54
+ def initialize(name:, pass_percentage:, return_value:, id:, salt:, conditions:, id_type:,
55
+ group_name: nil, config_delegate: nil, is_experiment_group: nil)
56
+ @name = name
57
+ @pass_percentage = pass_percentage.to_f
58
+ @return_value = JSON.parse(JSON.generate(return_value))
59
+ @id = id
60
+ @salt = salt
61
+ @conditions = conditions
62
+ @id_type = id_type
63
+ @group_name = group_name
64
+ @config_delegate = config_delegate
65
+ @is_experiment_group = is_experiment_group
66
+ end
67
+
68
+ def self.from_json(json)
69
+ new(
70
+ name: json[:name],
71
+ pass_percentage: json[:passPercentage],
72
+ return_value: json[:returnValue] || {},
73
+ id: json[:id],
74
+ salt: json[:salt],
75
+ conditions: json[:conditions]&.map do |condition|
76
+ APICondition.from_json(condition)
77
+ end,
78
+ id_type: json[:idType],
79
+ group_name: json[:groupName],
80
+ config_delegate: json[:configDelegate],
81
+ is_experiment_group: json[:isExperimentGroup]
82
+ )
83
+ end
84
+ end
85
+ end
86
+
87
+ module Statsig
88
+ class APICondition
89
+
90
+ attr_accessor :type, :target_value, :operator, :field, :additional_values, :id_type
91
+
92
+ def initialize(type:, target_value:, operator:, field:, additional_values:, id_type:)
93
+ @type = type.to_sym unless type.nil?
94
+ @target_value = target_value
95
+ @operator = operator.to_sym unless operator.nil?
96
+ @field = field
97
+ @additional_values = additional_values || {}
98
+ @id_type = id_type
99
+ end
100
+
101
+ def self.from_json(json)
102
+ operator = json[:operator]
103
+ unless operator.nil?
104
+ operator = operator&.downcase&.to_sym
105
+ unless Const::SUPPORTED_OPERATORS.include?(operator)
106
+ raise UnsupportedConfigException
107
+ end
108
+ end
109
+
110
+ type = json[:type]
111
+ unless type.nil?
112
+ type = type&.downcase&.to_sym
113
+ unless Const::SUPPORTED_CONDITION_TYPES.include?(type)
114
+ raise UnsupportedConfigException
115
+ end
116
+ end
117
+
118
+ new(
119
+ type: json[:type],
120
+ target_value: json[:targetValue],
121
+ operator: json[:operator],
122
+ field: json[:field],
123
+ additional_values: json[:additionalValues],
124
+ id_type: json[:idType]
125
+ )
126
+ end
127
+ end
128
+ end
@@ -1,167 +1,138 @@
1
- # typed: true
2
-
3
1
  require_relative 'hash_utils'
4
- require 'sorbet-runtime'
5
2
 
6
- $empty_eval_result = {
7
- :gate_value => false,
8
- :json_value => {},
9
- :rule_id => "",
10
- :is_experiment_group => false,
11
- :secondary_exposures => []
12
- }
3
+ require 'constants'
13
4
 
14
- module ClientInitializeHelpers
5
+ module Statsig
15
6
  class ResponseFormatter
16
- extend T::Sig
17
-
18
- def initialize(evaluator, user, hash, client_sdk_key)
19
- @evaluator = evaluator
20
- @user = user
21
- @specs = evaluator.spec_store.get_raw_specs
22
- @hash = hash
23
- @client_sdk_key = client_sdk_key
24
- end
25
-
26
- def get_responses(key)
27
- @specs[key]
28
- .map { |name, spec| to_response(name, spec) }
29
- .delete_if { |v| v.nil? }.to_h
30
- end
31
-
32
- private
33
-
34
- sig { params(secondary_exposures: T::Array[T::Hash[String, String]]).returns(T::Array[T::Hash[String, String]]) }
35
- def filter_segments_from_secondary_exposures(secondary_exposures)
36
- secondary_exposures.reject do |exposure|
37
- exposure['gate'].to_s.start_with?('segment:')
7
+ def self.get_responses(entities, evaluator, user, client_sdk_key, hash_algo, include_exposures: true)
8
+ result = {}
9
+ entities.each do |name, spec|
10
+ hashed_name, value = to_response(name, spec, evaluator, user, client_sdk_key, hash_algo, include_exposures)
11
+ if !hashed_name.nil? && !value.nil?
12
+ result[hashed_name] = value
13
+ end
38
14
  end
15
+
16
+ result
39
17
  end
40
18
 
41
- def to_response(config_name, config_spec)
42
- target_app_id = @evaluator.spec_store.get_app_id_for_sdk_key(@client_sdk_key)
43
- config_target_apps = config_spec['targetAppIDs']
19
+ def self.to_response(config_name, config_spec, evaluator, user, client_sdk_key, hash_algo, include_exposures)
20
+ target_app_id = evaluator.spec_store.get_app_id_for_sdk_key(client_sdk_key)
21
+ config_target_apps = config_spec.target_app_ids
44
22
 
45
23
  unless target_app_id.nil? || (!config_target_apps.nil? && config_target_apps.include?(target_app_id))
46
24
  return nil
47
25
  end
48
26
 
49
- eval_result = @evaluator.eval_spec(@user, config_spec)
50
- if eval_result.nil?
27
+ category = config_spec.type
28
+ entity_type = config_spec.entity
29
+ if entity_type == :segment || entity_type == :holdout
51
30
  return nil
52
31
  end
53
32
 
54
- safe_eval_result = eval_result == $fetch_from_server ? $empty_eval_result : {
55
- :gate_value => eval_result.gate_value,
56
- :json_value => eval_result.json_value,
57
- :rule_id => eval_result.rule_id,
58
- :group_name => eval_result.group_name,
59
- :id_type => eval_result.id_type,
60
- :config_delegate => eval_result.config_delegate,
61
- :is_experiment_group => eval_result.is_experiment_group,
62
- :secondary_exposures => filter_segments_from_secondary_exposures(eval_result.secondary_exposures),
63
- :undelegated_sec_exps => eval_result.undelegated_sec_exps
64
- }
65
-
66
- category = config_spec['type']
67
- entity_type = config_spec['entity']
33
+ eval_result = ConfigResult.new(
34
+ name: config_name,
35
+ disable_evaluation_details: true,
36
+ disable_exposures: !include_exposures
37
+ )
38
+ evaluator.eval_spec(user, config_spec, eval_result)
68
39
 
69
40
  result = {}
70
41
 
71
- case category
72
-
73
- when 'feature_gate'
74
- if entity_type == 'segment' || entity_type == 'holdout'
75
- return nil
76
- end
42
+ result[:id_type] = eval_result.id_type
43
+ unless eval_result.group_name.nil?
44
+ result[:group_name] = eval_result.group_name
45
+ end
77
46
 
78
- result['value'] = safe_eval_result[:gate_value]
79
- result["group_name"] = safe_eval_result[:group_name]
80
- result["id_type"] = safe_eval_result[:id_type]
81
- when 'dynamic_config'
82
- id_type = config_spec['idType']
83
- result['value'] = safe_eval_result[:json_value]
84
- result["group"] = safe_eval_result[:rule_id]
85
- result["group_name"] = safe_eval_result[:group_name]
86
- result["id_type"] = safe_eval_result[:id_type]
87
- result["is_device_based"] = id_type.is_a?(String) && id_type.downcase == 'stableid'
47
+ case category
48
+ when :feature_gate
49
+ result[:value] = eval_result.gate_value
50
+ when :dynamic_config
51
+ id_type = config_spec.id_type
52
+ result[:value] = eval_result.json_value
53
+ result[:group] = eval_result.rule_id
54
+ result[:is_device_based] = id_type.is_a?(String) && id_type.downcase == Statsig::Const::STABLEID
88
55
  else
89
56
  return nil
90
57
  end
91
58
 
92
- if entity_type == 'experiment'
93
- populate_experiment_fields(config_name, config_spec, safe_eval_result, result)
59
+ if entity_type == :experiment
60
+ populate_experiment_fields(name, config_spec, eval_result, result, evaluator)
94
61
  end
95
62
 
96
- if entity_type == 'layer'
97
- populate_layer_fields(config_spec, safe_eval_result, result)
98
- result.delete('id_type') # not exposed for layer configs in /initialize
63
+ if entity_type == :layer
64
+ populate_layer_fields(config_spec, eval_result, result, evaluator, hash_algo, include_exposures)
65
+ result.delete(:id_type) # not exposed for layer configs in /initialize
99
66
  end
100
67
 
101
- hashed_name = hash_name(config_name)
102
- [hashed_name, result.merge(
103
- {
104
- "name" => hashed_name,
105
- "rule_id" => safe_eval_result[:rule_id],
106
- "secondary_exposures" => clean_exposures(safe_eval_result[:secondary_exposures])
107
- }).compact]
68
+ hashed_name = hash_name(config_name, hash_algo)
69
+
70
+ result[:name] = hashed_name
71
+ result[:rule_id] = eval_result.rule_id
72
+
73
+ if include_exposures
74
+ result[:secondary_exposures] = clean_exposures(eval_result.secondary_exposures)
75
+ end
76
+
77
+ [hashed_name, result]
108
78
  end
109
79
 
110
- def clean_exposures(exposures)
80
+ def self.clean_exposures(exposures)
111
81
  seen = {}
112
82
  exposures.reject do |exposure|
113
- key = "#{exposure["gate"]}|#{exposure["gateValue"]}|#{exposure["ruleID"]}}"
83
+ key = "#{exposure[:gate]}|#{exposure[:gateValue]}|#{exposure[:ruleID]}}"
114
84
  should_reject = seen[key]
115
85
  seen[key] = true
116
86
  should_reject == true
117
87
  end
118
88
  end
119
89
 
120
- def populate_experiment_fields(config_name, config_spec, eval_result, result)
121
- result["is_user_in_experiment"] = eval_result[:is_experiment_group]
122
- result["is_experiment_active"] = config_spec['isActive'] == true
90
+ def self.populate_experiment_fields(config_name, config_spec, eval_result, result, evaluator)
91
+ result[:is_user_in_experiment] = eval_result.is_experiment_group
92
+ result[:is_experiment_active] = config_spec.is_active == true
123
93
 
124
- if config_spec['hasSharedParams'] != true
94
+ if config_spec.has_shared_params != true
125
95
  return
126
96
  end
127
97
 
128
- result["is_in_layer"] = true
129
- result["explicit_parameters"] = config_spec["explicitParameters"] || []
98
+ result[:is_in_layer] = true
99
+ result[:explicit_parameters] = config_spec.explicit_parameters || []
130
100
 
131
- layer_name = @specs[:experiment_to_layer][config_name]
132
- if layer_name.nil? || @specs[:layers][layer_name].nil?
101
+ layer_name = evaluator.spec_store.experiment_to_layer[config_name]
102
+ if layer_name.nil? || evaluator.spec_store.layers[layer_name].nil?
133
103
  return
134
104
  end
135
105
 
136
- layer = @specs[:layers][layer_name]
137
- result["value"] = layer["defaultValue"].merge(result["value"])
106
+ layer = evaluator.spec_store.layers[layer_name]
107
+ result[:value] = layer[:defaultValue].merge(result[:value])
138
108
  end
139
109
 
140
- def populate_layer_fields(config_spec, eval_result, result)
141
- delegate = eval_result[:config_delegate]
142
- result["explicit_parameters"] = config_spec["explicitParameters"] || []
110
+ def self.populate_layer_fields(config_spec, eval_result, result, evaluator, hash_algo, include_exposures)
111
+ delegate = eval_result.config_delegate
112
+ result[:explicit_parameters] = config_spec.explicit_parameters || []
143
113
 
144
114
  if delegate.nil? == false && delegate.empty? == false
145
- delegate_spec = @specs[:configs][delegate]
146
- delegate_result = @evaluator.eval_spec(@user, delegate_spec)
115
+ delegate_spec = evaluator.spec_store.configs[delegate]
147
116
 
148
- result["allocated_experiment_name"] = hash_name(delegate)
149
- result["is_user_in_experiment"] = delegate_result.is_experiment_group
150
- result["is_experiment_active"] = delegate_spec['isActive'] == true
151
- result["explicit_parameters"] = delegate_spec["explicitParameters"] || []
117
+ result[:allocated_experiment_name] = hash_name(delegate, hash_algo)
118
+ result[:is_user_in_experiment] = eval_result.is_experiment_group
119
+ result[:is_experiment_active] = delegate_spec.is_active == true
120
+ result[:explicit_parameters] = delegate_spec.explicit_parameters || []
152
121
  end
153
122
 
154
- result["undelegated_secondary_exposures"] = clean_exposures(eval_result[:undelegated_sec_exps] || [])
123
+ if include_exposures
124
+ result[:undelegated_secondary_exposures] = clean_exposures(eval_result.undelegated_sec_exps || [])
125
+ end
155
126
  end
156
127
 
157
- def hash_name(name)
158
- case @hash
159
- when 'none'
128
+ def self.hash_name(name, hash_algo)
129
+ case hash_algo
130
+ when Statsig::Const::NONE
160
131
  return name
161
- when 'sha256'
162
- return Statsig::HashUtils.sha256(name)
163
- when 'djb2'
132
+ when Statsig::Const::DJB2
164
133
  return Statsig::HashUtils.djb2(name)
134
+ else
135
+ return Statsig::HashUtils.sha256(name)
165
136
  end
166
137
  end
167
138
  end
data/lib/config_result.rb CHANGED
@@ -1,10 +1,5 @@
1
- # typed: true
2
-
3
- require 'sorbet-runtime'
4
-
5
1
  module Statsig
6
2
  class ConfigResult
7
- extend T::Sig
8
3
 
9
4
  attr_accessor :name
10
5
  attr_accessor :gate_value
@@ -19,25 +14,30 @@ module Statsig
19
14
  attr_accessor :group_name
20
15
  attr_accessor :id_type
21
16
  attr_accessor :target_app_ids
17
+ attr_accessor :disable_evaluation_details
18
+ attr_accessor :disable_exposures
22
19
 
23
20
  def initialize(
24
- name,
25
- gate_value = false,
26
- json_value = {},
27
- rule_id = '',
28
- secondary_exposures = [],
29
- config_delegate = nil,
30
- explicit_parameters = [],
21
+ name:,
22
+ gate_value: false,
23
+ json_value: nil,
24
+ rule_id: nil,
25
+ secondary_exposures: [],
26
+ config_delegate: nil,
27
+ explicit_parameters: nil,
31
28
  is_experiment_group: false,
32
29
  evaluation_details: nil,
33
30
  group_name: nil,
34
- id_type: '',
35
- target_app_ids: nil)
31
+ id_type: nil,
32
+ target_app_ids: nil,
33
+ disable_evaluation_details: false,
34
+ disable_exposures: false
35
+ )
36
36
  @name = name
37
37
  @gate_value = gate_value
38
38
  @json_value = json_value
39
39
  @rule_id = rule_id
40
- @secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
40
+ @secondary_exposures = secondary_exposures
41
41
  @undelegated_sec_exps = @secondary_exposures
42
42
  @config_delegate = config_delegate
43
43
  @explicit_parameters = explicit_parameters
@@ -46,9 +46,10 @@ module Statsig
46
46
  @group_name = group_name
47
47
  @id_type = id_type
48
48
  @target_app_ids = target_app_ids
49
+ @disable_evaluation_details = disable_evaluation_details
50
+ @disable_exposures = disable_exposures
49
51
  end
50
52
 
51
- sig { params(config_name: String, user_persisted_values: UserPersistedValues).returns(T.nilable(ConfigResult)) }
52
53
  def self.from_user_persisted_values(config_name, user_persisted_values)
53
54
  sticky_values = user_persisted_values[config_name]
54
55
  return nil if sticky_values.nil?
@@ -56,22 +57,6 @@ module Statsig
56
57
  from_hash(config_name, sticky_values)
57
58
  end
58
59
 
59
- sig { params(config_name: String, hash: Hash).returns(ConfigResult) }
60
- def self.from_hash(config_name, hash)
61
- new(
62
- config_name,
63
- hash['gate_value'],
64
- hash['json_value'],
65
- hash['rule_id'],
66
- hash['secondary_exposures'],
67
- evaluation_details: EvaluationDetails.persisted(hash['config_sync_time'], hash['init_time']),
68
- group_name: hash['group_name'],
69
- id_type: hash['id_type'],
70
- target_app_ids: hash['target_app_ids']
71
- )
72
- end
73
-
74
- sig { returns(Hash) }
75
60
  def to_hash
76
61
  {
77
62
  json_value: @json_value,
data/lib/constants.rb ADDED
@@ -0,0 +1,60 @@
1
+ module Statsig
2
+ module Const
3
+ EMPTY_STR = ''.freeze
4
+
5
+ SUPPORTED_CONDITION_TYPES = Set.new(%i[
6
+ public fail_gate pass_gate ip_based ua_based user_field
7
+ environment_field current_time user_bucket unit_id
8
+ ]).freeze
9
+
10
+ SUPPORTED_OPERATORS = Set.new(%i[
11
+ gt gte lt lte version_gt version_gte version_lt version_lte
12
+ version_eq version_neq any none any_case_sensitive none_case_sensitive
13
+ str_starts_with_any str_ends_with_any str_contains_any str_contains_none
14
+ str_matches eq neq before after on in_segment_list not_in_segment_list
15
+ ]).freeze
16
+
17
+ APP_VERSION = 'app_version'.freeze
18
+ APPVERSION = 'appversion'.freeze
19
+ BROWSER_NAME = 'browser_name'.freeze
20
+ BROWSER_VERSION = 'browser_version'.freeze
21
+ BROWSERNAME = 'browsername'.freeze
22
+ BROWSERVERSION = 'browserversion'.freeze
23
+ CML_SHA_256 = 'sha256'.freeze
24
+ CML_USER_ID = 'userID'.freeze
25
+ COUNTRY = 'country'.freeze
26
+ DEFAULT = 'default'.freeze
27
+ DISABLED = 'disabled'.freeze
28
+ DJB2 = 'djb2'.freeze
29
+ EMAIL = 'email'.freeze
30
+ FALSE = 'false'.freeze
31
+ IP = 'ip'.freeze
32
+ LAYER = :layer
33
+ LOCALE = 'locale'.freeze
34
+ NONE = 'none'.freeze
35
+ OS_NAME = 'os_name'.freeze
36
+ OS_VERSION = 'os_version'.freeze
37
+ OSNAME = 'osname'.freeze
38
+ OSVERSION = 'osversion'.freeze
39
+ OVERRIDE = 'override'.freeze
40
+ Q_RIGHT_CHEVRON = 'Q>'.freeze
41
+ STABLEID = 'stableid'.freeze
42
+ STATSIG_RUBY_SDK = 'statsig-ruby-sdk'.freeze
43
+ TRUE = 'true'.freeze
44
+ USER_AGENT = 'user_agent'.freeze
45
+ USER_ID = 'user_id'.freeze
46
+ USERAGENT = 'useragent'.freeze
47
+ USERID = 'userid'.freeze
48
+
49
+ # Persisted Evaluations
50
+ GATE_VALUE = 'gate_value'.freeze
51
+ JSON_VALUE = 'json_value'.freeze
52
+ RULE_ID = 'rule_id'.freeze
53
+ SECONDARY_EXPOSURES = 'secondary_exposures'.freeze
54
+ GROUP_NAME = 'group_name'.freeze
55
+ ID_TYPE = 'id_type'.freeze
56
+ TARGET_APP_IDS = 'target_app_ids'.freeze
57
+ CONFIG_SYNC_TIME = 'config_sync_time'.freeze
58
+ INIT_TIME = 'init_time'.freeze
59
+ end
60
+ end
data/lib/diagnostics.rb CHANGED
@@ -1,36 +1,15 @@
1
- # typed: true
2
-
3
- require 'sorbet-runtime'
4
-
5
1
  module Statsig
6
2
  class Diagnostics
7
- extend T::Sig
8
-
9
- sig { returns(String) }
10
- attr_accessor :context
11
-
12
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
13
3
  attr_reader :markers
14
4
 
15
- sig { returns(T::Hash[String, Numeric]) }
16
5
  attr_accessor :sample_rates
17
6
 
18
- def initialize(context)
19
- @context = context
20
- @markers = []
7
+ def initialize()
8
+ @markers = {:initialize => [], :api_call => [], :config_sync => []}
21
9
  @sample_rates = {}
22
10
  end
23
11
 
24
- sig do
25
- params(
26
- key: String,
27
- action: String,
28
- step: T.any(String, NilClass),
29
- tags: T::Hash[Symbol, T.untyped]
30
- ).void
31
- end
32
-
33
- def mark(key, action, step, tags)
12
+ def mark(key, action, step, tags, context)
34
13
  marker = {
35
14
  key: key,
36
15
  action: action,
@@ -44,87 +23,59 @@ module Statsig
44
23
  marker[key] = val
45
24
  end
46
25
  end
47
- @markers.push(marker)
26
+ if @markers[context].nil?
27
+ @markers[context] = []
28
+ end
29
+ @markers[context].push(marker)
48
30
  end
49
31
 
50
- sig do
51
- params(
52
- key: String,
53
- step: T.any(String, NilClass),
54
- tags: T::Hash[Symbol, T.untyped]
55
- ).returns(Tracker)
56
- end
57
- def track(key, step = nil, tags = {})
58
- tracker = Tracker.new(self, key, step, tags)
32
+ def track(context, key, step = nil, tags = {})
33
+ tracker = Tracker.new(self, context, key, step, tags)
59
34
  tracker.start(**tags)
60
35
  tracker
61
36
  end
62
37
 
63
- sig { returns(T::Hash[Symbol, T.untyped]) }
64
-
65
- def serialize
66
- {
67
- context: @context.clone,
68
- markers: @markers.clone
69
- }
70
- end
71
-
72
- def serialize_with_sampling
73
- marker_keys = @markers.map { |e| e[:key] }
38
+ def serialize_with_sampling(context)
39
+ marker_keys = @markers[context].map { |e| e[:key] }
74
40
  unique_marker_keys = marker_keys.uniq { |e| e }
75
41
  sampled_marker_keys = unique_marker_keys.select do |key|
76
42
  @sample_rates.key?(key) && !self.class.sample(@sample_rates[key])
77
43
  end
78
- final_markers = @markers.select do |marker|
44
+ final_markers = @markers[context].select do |marker|
79
45
  !sampled_marker_keys.include?(marker[:key])
80
46
  end
81
47
  {
82
- context: @context.clone,
83
- markers: final_markers
48
+ context: context.clone,
49
+ markers: final_markers.clone
84
50
  }
85
51
  end
86
52
 
87
- def clear_markers
88
- @markers.clear
53
+ def clear_markers(context)
54
+ @markers[context].clear
89
55
  end
90
56
 
91
57
  def self.sample(rate_over_ten_thousand)
92
58
  rand * 10_000 < rate_over_ten_thousand
93
59
  end
94
60
 
95
- class Context
96
- INITIALIZE = 'initialize'.freeze
97
- CONFIG_SYNC = 'config_sync'.freeze
98
- API_CALL = 'api_call'.freeze
99
- end
100
-
101
61
  API_CALL_KEYS = %w[check_gate get_config get_experiment get_layer].freeze
102
62
 
103
63
  class Tracker
104
- extend T::Sig
105
-
106
- sig do
107
- params(
108
- diagnostics: Diagnostics,
109
- key: String,
110
- step: T.any(String, NilClass),
111
- tags: T::Hash[Symbol, T.untyped]
112
- ).void
113
- end
114
- def initialize(diagnostics, key, step, tags = {})
64
+ def initialize(diagnostics, context, key, step, tags = {})
115
65
  @diagnostics = diagnostics
66
+ @context = context
116
67
  @key = key
117
68
  @step = step
118
69
  @tags = tags
119
70
  end
120
71
 
121
72
  def start(**tags)
122
- @diagnostics.mark(@key, 'start', @step, tags.nil? ? {} : tags.merge(@tags))
73
+ @diagnostics.mark(@key, 'start', @step, tags.nil? ? {} : tags.merge(@tags), @context)
123
74
  end
124
75
 
125
76
  def end(**tags)
126
- @diagnostics.mark(@key, 'end', @step, tags.nil? ? {} : tags.merge(@tags))
77
+ @diagnostics.mark(@key, 'end', @step, tags.nil? ? {} : tags.merge(@tags), @context)
127
78
  end
128
79
  end
129
80
  end
130
- end
81
+ end