statsig 2.9.2 → 2.9.4
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.
- checksums.yaml +4 -4
- data/lib/client_initialize_helpers.rb +2 -1
- data/lib/config_result.rb +4 -1
- data/lib/constants.rb +2 -0
- data/lib/evaluator.rb +36 -2
- data/lib/network.rb +14 -6
- data/lib/statsig.rb +1 -1
- data/lib/statsig_driver.rb +7 -4
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 96c137e20d5bc087e6b3108ad346f3882a8f981c2fc18e95270af8511230f932
|
|
4
|
+
data.tar.gz: daeaec468e9fa579f7a5ef80085bf19a49175d25985ae7d2128ed1c767e073bb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 76055e14d07eab81f94c6ad559e3378da16860a8c60fc3dc6a1118fa7621c61f53d7b4921e0a70856e1195aa954b23fb188812fe9ba16f4c6db3059c3ad59f1c
|
|
7
|
+
data.tar.gz: ad835e17a3ee00318cbf3ee7ccbcf94b7ef09dd05a08163b6ff1308d79040593175fb3d08b44d716c015edeae55d5949f1ad3515c74eed13ae16c5c709b84069
|
|
@@ -51,7 +51,8 @@ module Statsig
|
|
|
51
51
|
name: config_name,
|
|
52
52
|
disable_evaluation_details: true,
|
|
53
53
|
disable_exposures: !include_exposures,
|
|
54
|
-
include_local_overrides: include_local_overrides
|
|
54
|
+
include_local_overrides: include_local_overrides,
|
|
55
|
+
disable_nested_experiment_evaluation: true
|
|
55
56
|
)
|
|
56
57
|
evaluator.eval_spec(config_name_str, user, config_spec, eval_result)
|
|
57
58
|
else
|
data/lib/config_result.rb
CHANGED
|
@@ -22,6 +22,7 @@ module Statsig
|
|
|
22
22
|
attr_accessor :sampling_rate
|
|
23
23
|
attr_accessor :has_seen_analytical_gates
|
|
24
24
|
attr_accessor :override_config_name
|
|
25
|
+
attr_accessor :disable_nested_experiment_evaluation
|
|
25
26
|
|
|
26
27
|
def initialize(
|
|
27
28
|
name:,
|
|
@@ -43,7 +44,8 @@ module Statsig
|
|
|
43
44
|
forward_all_exposures: false,
|
|
44
45
|
sampling_rate: nil,
|
|
45
46
|
has_seen_analytical_gates: false,
|
|
46
|
-
override_config_name: nil
|
|
47
|
+
override_config_name: nil,
|
|
48
|
+
disable_nested_experiment_evaluation: false
|
|
47
49
|
)
|
|
48
50
|
@name = name
|
|
49
51
|
@gate_value = gate_value
|
|
@@ -66,6 +68,7 @@ module Statsig
|
|
|
66
68
|
@sampling_rate = sampling_rate
|
|
67
69
|
@has_seen_analytical_gates = has_seen_analytical_gates
|
|
68
70
|
@override_config_name = override_config_name
|
|
71
|
+
@disable_nested_experiment_evaluation = disable_nested_experiment_evaluation
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
def self.from_user_persisted_values(config_name, user_persisted_values)
|
data/lib/constants.rb
CHANGED
|
@@ -7,6 +7,7 @@ module Statsig
|
|
|
7
7
|
SUPPORTED_CONDITION_TYPES = Set.new(%i[
|
|
8
8
|
public fail_gate pass_gate ip_based ua_based user_field
|
|
9
9
|
environment_field current_time user_bucket unit_id
|
|
10
|
+
experiment_group
|
|
10
11
|
]).freeze
|
|
11
12
|
|
|
12
13
|
SUPPORTED_OPERATORS = Set.new(%i[
|
|
@@ -87,6 +88,7 @@ module Statsig
|
|
|
87
88
|
CND_MULTI_PASS_GATE = 'multi_pass_gate'.freeze
|
|
88
89
|
CND_MULTI_FAIL_GATE = 'multi_fail_gate'.freeze
|
|
89
90
|
CND_CURRENT_TIME = 'current_time'.freeze
|
|
91
|
+
CND_EXPERIMENT_GROUP = 'experiment_group'.freeze
|
|
90
92
|
CND_ENVIRONMENT_FIELD = 'environment_field'.freeze
|
|
91
93
|
CND_USER_BUCKET = 'user_bucket'.freeze
|
|
92
94
|
CND_UNIT_ID = 'unit_id'.freeze
|
data/lib/evaluator.rb
CHANGED
|
@@ -14,6 +14,12 @@ require_relative 'user_persistent_storage_utils'
|
|
|
14
14
|
module Statsig
|
|
15
15
|
class Evaluator
|
|
16
16
|
|
|
17
|
+
EXPOSURE_LOGGING_CONDITION_TYPES = [
|
|
18
|
+
Const::CND_PASS_GATE,
|
|
19
|
+
Const::CND_FAIL_GATE,
|
|
20
|
+
Const::CND_EXPERIMENT_GROUP
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
17
23
|
attr_accessor :spec_store
|
|
18
24
|
|
|
19
25
|
attr_accessor :gate_overrides
|
|
@@ -26,7 +32,7 @@ module Statsig
|
|
|
26
32
|
|
|
27
33
|
attr_accessor :persistent_storage_utils
|
|
28
34
|
|
|
29
|
-
def initialize(store, options, persistent_storage_utils)
|
|
35
|
+
def initialize(store, options, persistent_storage_utils, logger = nil)
|
|
30
36
|
UAParser.initialize_async
|
|
31
37
|
CountryLookup.initialize_async
|
|
32
38
|
|
|
@@ -36,6 +42,7 @@ module Statsig
|
|
|
36
42
|
@experiment_overrides = {}
|
|
37
43
|
@options = options
|
|
38
44
|
@persistent_storage_utils = persistent_storage_utils
|
|
45
|
+
@logger = logger
|
|
39
46
|
end
|
|
40
47
|
|
|
41
48
|
def maybe_restart_background_threads
|
|
@@ -689,8 +696,10 @@ module Statsig
|
|
|
689
696
|
eval_condition(user, condition, end_result)
|
|
690
697
|
end
|
|
691
698
|
|
|
699
|
+
# Conditions that log an exposure as a side effect are never memoized, or the
|
|
700
|
+
# repeat evaluations would silently drop their exposures.
|
|
692
701
|
if !@options.disable_evaluation_memoization &&
|
|
693
|
-
condition && condition[:type]
|
|
702
|
+
condition && !EXPOSURE_LOGGING_CONDITION_TYPES.include?(condition[:type])
|
|
694
703
|
eval_rule_memo[condition_hash] = result
|
|
695
704
|
end
|
|
696
705
|
|
|
@@ -742,6 +751,8 @@ module Statsig
|
|
|
742
751
|
return type == Const::CND_PASS_GATE ? result : !result
|
|
743
752
|
when Const::CND_MULTI_PASS_GATE, Const::CND_MULTI_FAIL_GATE
|
|
744
753
|
return eval_nested_gates(target, type, user, end_result)
|
|
754
|
+
when Const::CND_EXPERIMENT_GROUP
|
|
755
|
+
value = eval_experiment_group(field, user, end_result)
|
|
745
756
|
when Const::CND_IP_BASED
|
|
746
757
|
value = get_value_from_user(user, field) || get_value_from_ip(user, field)
|
|
747
758
|
when Const::CND_UA_BASED
|
|
@@ -921,6 +932,29 @@ module Statsig
|
|
|
921
932
|
gate_value
|
|
922
933
|
end
|
|
923
934
|
|
|
935
|
+
# The condition's field carries the name of a parent experiment. The user's group
|
|
936
|
+
# in that experiment becomes the value the operator compares against. Mirrors
|
|
937
|
+
# statsig-rust's evaluate_experiment_group, which evaluates the parent through the
|
|
938
|
+
# public get_experiment API: a full evaluation that logs its own experiment
|
|
939
|
+
# exposure and contributes nothing to the outer result's secondary exposures.
|
|
940
|
+
def eval_experiment_group(experiment_name, user, end_result)
|
|
941
|
+
return nil if end_result.disable_nested_experiment_evaluation
|
|
942
|
+
return nil unless experiment_name.is_a?(String) && !experiment_name.empty?
|
|
943
|
+
|
|
944
|
+
nested_result = ConfigResult.new(
|
|
945
|
+
name: experiment_name,
|
|
946
|
+
disable_exposures: end_result.disable_exposures,
|
|
947
|
+
disable_evaluation_details: end_result.disable_evaluation_details
|
|
948
|
+
)
|
|
949
|
+
get_config(user, experiment_name, nested_result)
|
|
950
|
+
|
|
951
|
+
unless end_result.disable_exposures
|
|
952
|
+
@logger&.log_config_exposure(user, nested_result)
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
nested_result.group_name
|
|
956
|
+
end
|
|
957
|
+
|
|
924
958
|
def eval_nested_gates(gate_names, condition_type, user, end_result)
|
|
925
959
|
has_passing_gate = false
|
|
926
960
|
is_multi_pass_gate_type = condition_type == Const::CND_MULTI_PASS_GATE
|
data/lib/network.rb
CHANGED
|
@@ -19,6 +19,14 @@ module Statsig
|
|
|
19
19
|
|
|
20
20
|
class Network
|
|
21
21
|
|
|
22
|
+
# Background config-spec sync retries transient failures with a short, bounded
|
|
23
|
+
# exponential backoff, matching statsig-server-core's background sync strategy
|
|
24
|
+
# (3 retries; 2^n * 100ms => 200ms, 400ms, 800ms). These are fixed rather than
|
|
25
|
+
# user-configurable, mirroring server-core where the values are hardcoded.
|
|
26
|
+
CONFIG_SYNC_RETRY_LIMIT = 3
|
|
27
|
+
CONFIG_SYNC_RETRY_BACKOFF_SECONDS = 0.2
|
|
28
|
+
CONFIG_SYNC_RETRY_BACKOFF_MULTIPLIER = 2
|
|
29
|
+
|
|
22
30
|
def initialize(server_secret, options, backoff_mult = 10)
|
|
23
31
|
super()
|
|
24
32
|
@options = options
|
|
@@ -42,7 +50,7 @@ module Statsig
|
|
|
42
50
|
if context == 'initialize'
|
|
43
51
|
return get(dcs_url, @options.initialize_retry_limit)
|
|
44
52
|
end
|
|
45
|
-
get(dcs_url)
|
|
53
|
+
get(dcs_url, CONFIG_SYNC_RETRY_LIMIT, CONFIG_SYNC_RETRY_BACKOFF_SECONDS, CONFIG_SYNC_RETRY_BACKOFF_MULTIPLIER)
|
|
46
54
|
end
|
|
47
55
|
|
|
48
56
|
def download_config_specs_fallback(since_time, context)
|
|
@@ -84,8 +92,8 @@ module Statsig
|
|
|
84
92
|
request(:GET, url, nil, 0, 1, false, 0, { 'Range' => "bytes=#{start_byte}-" }, false)
|
|
85
93
|
end
|
|
86
94
|
|
|
87
|
-
def get(url, retries = 0, backoff = 1)
|
|
88
|
-
request(:GET, url, nil, retries, backoff)
|
|
95
|
+
def get(url, retries = 0, backoff = 1, backoff_multiplier = @backoff_multiplier)
|
|
96
|
+
request(:GET, url, nil, retries, backoff, false, 0, {}, true, backoff_multiplier)
|
|
89
97
|
end
|
|
90
98
|
|
|
91
99
|
def post(url, body, retries = 0, backoff = 1, zipped = false, event_count = 0)
|
|
@@ -106,7 +114,7 @@ module Statsig
|
|
|
106
114
|
end
|
|
107
115
|
end
|
|
108
116
|
|
|
109
|
-
def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true)
|
|
117
|
+
def request(method, url, body, retries = 0, backoff = 1, zipped = false, event_count = 0, extra_headers = {}, use_statsig_headers = true, backoff_multiplier = @backoff_multiplier)
|
|
110
118
|
if @local_mode
|
|
111
119
|
return nil, nil
|
|
112
120
|
end
|
|
@@ -146,7 +154,7 @@ module Statsig
|
|
|
146
154
|
return nil, e unless retries.positive?
|
|
147
155
|
|
|
148
156
|
sleep backoff_adjusted
|
|
149
|
-
return request(method, url, body, retries - 1, backoff *
|
|
157
|
+
return request(method, url, body, retries - 1, backoff * backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers, backoff_multiplier)
|
|
150
158
|
end
|
|
151
159
|
return res, nil if res.status.success?
|
|
152
160
|
|
|
@@ -157,7 +165,7 @@ module Statsig
|
|
|
157
165
|
|
|
158
166
|
## status code retry
|
|
159
167
|
sleep backoff_adjusted
|
|
160
|
-
request(method, url, body, retries - 1, backoff *
|
|
168
|
+
request(method, url, body, retries - 1, backoff * backoff_multiplier, zipped, event_count, extra_headers, use_statsig_headers, backoff_multiplier)
|
|
161
169
|
end
|
|
162
170
|
|
|
163
171
|
private
|
data/lib/statsig.rb
CHANGED
data/lib/statsig_driver.rb
CHANGED
|
@@ -34,12 +34,13 @@ class StatsigDriver
|
|
|
34
34
|
tracker = @diagnostics.track('initialize', 'overall')
|
|
35
35
|
@options = options || StatsigOptions.new
|
|
36
36
|
@shutdown = false
|
|
37
|
+
@shutdown_message_logged = false
|
|
37
38
|
@secret_key = secret_key
|
|
38
39
|
@net = Statsig::Network.new(secret_key, @options)
|
|
39
40
|
@logger = Statsig::StatsigLogger.new(@net, @options, @err_boundary, @sdk_configs)
|
|
40
41
|
@persistent_storage_utils = Statsig::UserPersistentStorageUtils.new(@options)
|
|
41
42
|
@store = Statsig::SpecStore.new(@net, @options, error_callback, @diagnostics, @err_boundary, @logger, secret_key, @sdk_configs)
|
|
42
|
-
@evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils)
|
|
43
|
+
@evaluator = Statsig::Evaluator.new(@store, @options, @persistent_storage_utils, @logger)
|
|
43
44
|
tracker.end(success: true)
|
|
44
45
|
|
|
45
46
|
@logger.log_diagnostics_event(@diagnostics, 'initialize')
|
|
@@ -460,8 +461,10 @@ class StatsigDriver
|
|
|
460
461
|
end
|
|
461
462
|
|
|
462
463
|
def check_shutdown
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
464
|
+
return unless @shutdown
|
|
465
|
+
return if @shutdown_message_logged
|
|
466
|
+
|
|
467
|
+
@shutdown_message_logged = true
|
|
468
|
+
puts '[Statsig]: SDK has been shutdown. Updates in the Statsig Console will no longer reflect.'
|
|
466
469
|
end
|
|
467
470
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: statsig
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.9.
|
|
4
|
+
version: 2.9.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Statsig, Inc
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|