statsig 1.24.5 → 1.25.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: 1565b50f9e48bfcd6c456bc8e1681a9adbd523287f656b042c3cce803063d3b6
4
- data.tar.gz: 8dbf5c5879173b3c44e0e155c8e6e649af5e0cb184e1fdd18a47cc8875fb32c0
3
+ metadata.gz: 267f2a16a3bb05a6b046d493bfe98f00b7c083f387eba7862641801ec0579b2b
4
+ data.tar.gz: a20de08fe3b12bcba40795ec1ff53ce5d3c2c27aca3ab41d72541a49e730414c
5
5
  SHA512:
6
- metadata.gz: f8c6e02873f9274b49a42fe94f21d67eec6d2ba720841d107d1b0b3e7f39b3a208fc4bae478337829359e25d109f5e7409dffbd143e802ab5e1f1c2b0d08fcf0
7
- data.tar.gz: f75079d1ba3837d66a8386aa106d2bb4f75e78371dcc53cf09e1921610841d81a958e351e1f1a2f5786ebf0c44c43215a709edcb1e21dd45b563f8b2f4f8004d
6
+ metadata.gz: e7afa3f47ea19f3d759a648f12bd98c8e56c1c757f2c86bc72859ace8ca081e205af90f315cd26c08598fed408a8b4163b452e2312066120d5ead9644f1028ec
7
+ data.tar.gz: d671a37cf7ad21212d71ad90c8fddab13110641a278c475c6f4cec3ff922c0d6b6bceaa1367b902933db481be56601af2ab203c03768a65e9dd959fb900a2438
data/lib/evaluator.rb CHANGED
@@ -6,7 +6,7 @@ require 'evaluation_helpers'
6
6
  require 'client_initialize_helpers'
7
7
  require 'spec_store'
8
8
  require 'time'
9
- require 'user_agent_parser'
9
+ require 'ua_parser'
10
10
  require 'evaluation_details'
11
11
  require 'user_agent_parser/operating_system'
12
12
 
@@ -19,8 +19,8 @@ module Statsig
19
19
 
20
20
  def initialize(network, options, error_callback, init_diagnostics = nil)
21
21
  @spec_store = Statsig::SpecStore.new(network, options, error_callback, init_diagnostics)
22
- @ua_parser = UserAgentParser::Parser.new
23
- CountryLookup.initialize
22
+ UAParser.initialize_async
23
+ CountryLookup.initialize_async
24
24
 
25
25
  @gate_overrides = {}
26
26
  @config_overrides = {}
@@ -447,16 +447,18 @@ module Statsig
447
447
  ua = get_value_from_user(user, 'userAgent')
448
448
  return nil unless ua.is_a?(String)
449
449
 
450
- parsed = @ua_parser.parse ua
451
- os = parsed.os
452
450
  case field.downcase
453
451
  when 'os_name', 'osname'
452
+ os = UAParser.parse_os(ua)
454
453
  return os&.family
455
454
  when 'os_version', 'osversion'
455
+ os = UAParser.parse_os(ua)
456
456
  return os&.version unless os&.version.nil?
457
457
  when 'browser_name', 'browsername'
458
+ parsed = UAParser.parse_ua(ua)
458
459
  return parsed.family
459
460
  when 'browser_version', 'browserversion'
461
+ parsed = UAParser.parse_ua(ua)
460
462
  return parsed.version.to_s
461
463
  else
462
464
  nil
data/lib/layer.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # typed: false
2
2
 
3
+ require 'sorbet-runtime'
3
4
  ##
4
5
  # Contains the current values from Statsig.
5
6
  # Will contain layer default values for all shared parameters in that layer.
data/lib/network.rb CHANGED
@@ -33,6 +33,8 @@ module Statsig
33
33
  @local_mode = options.local_mode
34
34
  @timeout = options.network_timeout
35
35
  @backoff_multiplier = backoff_mult
36
+ @post_logs_retry_backoff = options.post_logs_retry_backoff
37
+ @post_logs_retry_limit = options.post_logs_retry_limit
36
38
  @session_id = SecureRandom.uuid
37
39
  end
38
40
 
@@ -57,18 +59,26 @@ module Statsig
57
59
  if @timeout
58
60
  http = http.timeout(@timeout)
59
61
  end
62
+ backoff_adjusted = backoff > 10 ? backoff += Random.rand(10) : backoff # to deter overlap
63
+ if @post_logs_retry_backoff
64
+ if @post_logs_retry_backoff.is_a? Integer
65
+ backoff_adjusted = @post_logs_retry_backoff
66
+ else
67
+ backoff_adjusted = @post_logs_retry_backoff.call(retries)
68
+ end
69
+ end
60
70
  begin
61
71
  res = http.post(@api + endpoint, body: body)
62
72
  rescue StandardError => e
63
73
  ## network error retry
64
74
  return nil, e unless retries > 0
65
- sleep backoff
75
+ sleep backoff_adjusted
66
76
  return post_helper(endpoint, body, retries - 1, backoff * @backoff_multiplier)
67
77
  end
68
78
  return res, nil if res.status.success?
69
79
  return nil, NetworkError.new("Got an exception when making request to #{@api + endpoint}: #{res.to_s}", res.status.to_i) unless retries > 0 && $retry_codes.include?(res.code)
70
80
  ## status code retry
71
- sleep backoff
81
+ sleep backoff_adjusted
72
82
  post_helper(endpoint, body, retries - 1, backoff * @backoff_multiplier)
73
83
  end
74
84
 
@@ -97,7 +107,7 @@ module Statsig
97
107
  def post_logs(events)
98
108
  begin
99
109
  json_body = JSON.generate({ 'events' => events, 'statsigMetadata' => Statsig.get_statsig_metadata })
100
- post_helper('log_event', json_body, 5)
110
+ post_helper('log_event', json_body, @post_logs_retry_limit)
101
111
  rescue
102
112
  end
103
113
  end
data/lib/spec_store.rb CHANGED
@@ -32,6 +32,7 @@ module Statsig
32
32
 
33
33
  @id_list_thread_pool = Concurrent::FixedThreadPool.new(
34
34
  options.idlist_threadpool_size,
35
+ name: 'statsig-idlist',
35
36
  max_queue: 100,
36
37
  fallback_policy: :discard,
37
38
  )
@@ -309,6 +310,16 @@ module Statsig
309
310
 
310
311
  init_diagnostics&.mark("get_id_lists", "start", "process", new_id_lists.length)
311
312
 
313
+ delete_lists = []
314
+ local_id_lists.each do |list_name, list|
315
+ unless new_id_lists.key? list_name
316
+ delete_lists.push list_name
317
+ end
318
+ end
319
+ delete_lists.each do |list_name|
320
+ local_id_lists.delete list_name
321
+ end
322
+
312
323
  new_id_lists.each do |list_name, list|
313
324
  new_list = IDList.new(list)
314
325
  local_list = get_id_list(list_name)
@@ -346,21 +357,7 @@ module Statsig
346
357
  end
347
358
 
348
359
  result = Concurrent::Promise.all?(*tasks).execute.wait(@id_lists_sync_interval)
349
- if result.state != :fulfilled
350
- init_diagnostics&.mark("get_id_lists", "end", "process", false)
351
- return # timed out
352
- end
353
-
354
- delete_lists = []
355
- local_id_lists.each do |list_name, list|
356
- unless new_id_lists.key? list_name
357
- delete_lists.push list_name
358
- end
359
- end
360
- delete_lists.each do |list_name|
361
- local_id_lists.delete list_name
362
- end
363
- init_diagnostics&.mark("get_id_lists", "end", "process", true)
360
+ init_diagnostics&.mark("get_id_lists", "end", "process", result.state == :fulfilled)
364
361
  end
365
362
 
366
363
  def get_single_id_list_from_adapter(list)
data/lib/statsig.rb CHANGED
@@ -148,7 +148,7 @@ module Statsig
148
148
  @shared_instance&.get_layer(user, layer_name, StatsigDriver::GetLayerOptions.new(log_exposure: false))
149
149
  end
150
150
 
151
- sig { params(user: StatsigUser, layer_name: String, parameter_name: String).returns(Layer) }
151
+ sig { params(user: StatsigUser, layer_name: String, parameter_name: String).void }
152
152
  ##
153
153
  # Logs an exposure event for the parameter in the given layer
154
154
  #
@@ -227,7 +227,7 @@ module Statsig
227
227
  def self.get_statsig_metadata
228
228
  {
229
229
  'sdkType' => 'ruby-server',
230
- 'sdkVersion' => '1.24.5',
230
+ 'sdkVersion' => '1.24.6',
231
231
  }
232
232
  end
233
233
 
@@ -170,7 +170,6 @@ class StatsigDriver
170
170
  event.user = user
171
171
  event.value = value
172
172
  event.metadata = metadata
173
- event.statsig_metadata = Statsig.get_statsig_metadata
174
173
  @logger.log_event(event)
175
174
  })
176
175
  end
@@ -199,6 +198,7 @@ class StatsigDriver
199
198
  # @return [Hash]
200
199
  def get_client_initialize_response(user)
201
200
  @err_boundary.capture(-> {
201
+ validate_user(user)
202
202
  normalize_user(user)
203
203
  @evaluator.get_client_initialize_response(user)
204
204
  }, -> { nil })
data/lib/statsig_event.rb CHANGED
@@ -1,9 +1,6 @@
1
1
  # typed: true
2
2
  class StatsigEvent
3
- attr_accessor :value
4
- attr_accessor :metadata
5
- attr_accessor :statsig_metadata
6
- attr_accessor :secondary_exposures
3
+ attr_accessor :value, :metadata, :statsig_metadata, :secondary_exposures
7
4
  attr_reader :user
8
5
 
9
6
  def initialize(event_name)
@@ -13,6 +10,7 @@ class StatsigEvent
13
10
  @secondary_exposures = nil
14
11
  @user = nil
15
12
  @time = (Time.now.to_f * 1000).to_i
13
+ @statsig_metadata = Statsig.get_statsig_metadata
16
14
  end
17
15
 
18
16
  def user=(value)
@@ -15,11 +15,12 @@ module Statsig
15
15
  @options = options
16
16
 
17
17
  @logging_pool = Concurrent::ThreadPoolExecutor.new(
18
- min_threads: [2, Concurrent.processor_count].min,
19
- max_threads: [2, Concurrent.processor_count].max,
18
+ name: 'statsig-logger',
19
+ min_threads: @options.logger_threadpool_size,
20
+ max_threads: @options.logger_threadpool_size,
20
21
  # max jobs pending before we start dropping
21
- max_queue: [2, Concurrent.processor_count].max * 5,
22
- fallback_policy: :discard,
22
+ max_queue: 100,
23
+ fallback_policy: :discard
23
24
  )
24
25
 
25
26
  @background_flush = periodic_flush
@@ -44,7 +45,6 @@ module Statsig
44
45
  }
45
46
  return false if not is_unique_exposure(user, $gate_exposure_event, metadata)
46
47
  event.metadata = metadata
47
- event.statsig_metadata = Statsig.get_statsig_metadata
48
48
 
49
49
  event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
50
50
 
@@ -62,7 +62,6 @@ module Statsig
62
62
  }
63
63
  return false if not is_unique_exposure(user, $config_exposure_event, metadata)
64
64
  event.metadata = metadata
65
- event.statsig_metadata = Statsig.get_statsig_metadata
66
65
  event.secondary_exposures = secondary_exposures.is_a?(Array) ? secondary_exposures : []
67
66
 
68
67
  safe_add_eval_details(eval_details, event)
@@ -90,7 +89,6 @@ module Statsig
90
89
  }
91
90
  return false if not is_unique_exposure(user, $layer_exposure_event, metadata)
92
91
  event.metadata = metadata
93
- event.statsig_metadata = Statsig.get_statsig_metadata
94
92
  event.secondary_exposures = exposures.is_a?(Array) ? exposures : []
95
93
 
96
94
  safe_add_eval_details(config_evaluation.evaluation_details, event)
@@ -109,7 +107,7 @@ module Statsig
109
107
  Thread.new do
110
108
  loop do
111
109
  sleep @options.logging_interval_seconds
112
- flush
110
+ flush_async
113
111
  @interval += 1
114
112
  @deduper.clear if @interval % 2 == 0
115
113
  end
@@ -64,6 +64,11 @@ class StatsigOptions
64
64
  # default: 3
65
65
  attr_accessor :idlist_threadpool_size
66
66
 
67
+ sig { returns(Integer) }
68
+ # The number of threads allocated to posting event logs.
69
+ # default: 3
70
+ attr_accessor :logger_threadpool_size
71
+
67
72
  sig { returns(T::Boolean) }
68
73
  # Should diagnostics be logged. These include performance metrics for initialize.
69
74
  # default: false
@@ -79,6 +84,15 @@ class StatsigOptions
79
84
  # Number of seconds before a network call is timed out
80
85
  attr_accessor :network_timeout
81
86
 
87
+ sig { returns(Integer) }
88
+ # Number of times to retry sending a batch of failed log events
89
+ attr_accessor :post_logs_retry_limit
90
+
91
+ sig { returns(T.any(Method, Proc, Integer, NilClass)) }
92
+ # The number of seconds, or a function that returns the number of seconds based on the number of retries remaining
93
+ # which overrides the default backoff time between retries
94
+ attr_accessor :post_logs_retry_backoff
95
+
82
96
  sig do
83
97
  params(
84
98
  environment: T.any(T::Hash[String, String], NilClass),
@@ -92,9 +106,12 @@ class StatsigOptions
92
106
  rules_updated_callback: T.any(Method, Proc, NilClass),
93
107
  data_store: T.any(Statsig::Interfaces::IDataStore, NilClass),
94
108
  idlist_threadpool_size: Integer,
109
+ logger_threadpool_size: Integer,
95
110
  disable_diagnostics_logging: T::Boolean,
96
111
  disable_sorbet_logging_handlers: T::Boolean,
97
- network_timeout: T.any(Integer, NilClass)
112
+ network_timeout: T.any(Integer, NilClass),
113
+ post_logs_retry_limit: Integer,
114
+ post_logs_retry_backoff: T.any(Method, Proc, Integer, NilClass)
98
115
  ).void
99
116
  end
100
117
 
@@ -110,9 +127,12 @@ class StatsigOptions
110
127
  rules_updated_callback: nil,
111
128
  data_store: nil,
112
129
  idlist_threadpool_size: 3,
130
+ logger_threadpool_size: 3,
113
131
  disable_diagnostics_logging: false,
114
132
  disable_sorbet_logging_handlers: false,
115
- network_timeout: nil)
133
+ network_timeout: nil,
134
+ post_logs_retry_limit: 3,
135
+ post_logs_retry_backoff: nil)
116
136
  @environment = environment.is_a?(Hash) ? environment : nil
117
137
  @api_url_base = api_url_base
118
138
  @rulesets_sync_interval = rulesets_sync_interval
@@ -124,8 +144,11 @@ class StatsigOptions
124
144
  @rules_updated_callback = rules_updated_callback
125
145
  @data_store = data_store
126
146
  @idlist_threadpool_size = idlist_threadpool_size
147
+ @logger_threadpool_size = logger_threadpool_size
127
148
  @disable_diagnostics_logging = disable_diagnostics_logging
128
149
  @disable_sorbet_logging_handlers = disable_sorbet_logging_handlers
129
150
  @network_timeout = network_timeout
151
+ @post_logs_retry_limit = post_logs_retry_limit
152
+ @post_logs_retry_backoff = post_logs_retry_backoff
130
153
  end
131
154
  end
data/lib/ua_parser.rb ADDED
@@ -0,0 +1,57 @@
1
+ require 'user_agent_parser'
2
+
3
+ module UAParser
4
+ class Parser
5
+ def initialize
6
+ @ua_parser = UserAgentParser::Parser.new
7
+ end
8
+
9
+ def parse_os(*args)
10
+ @ua_parser.parse_os(*args)
11
+ end
12
+
13
+ def parse_ua(*args)
14
+ @ua_parser.parse_ua(*args)
15
+ end
16
+
17
+ def parse_device(*args)
18
+ @ua_parser.parse_device(*args)
19
+ end
20
+ end
21
+
22
+ def self.initialize
23
+ if !@initialize_bg_thread.nil? && @initialize_bg_thread.alive?
24
+ @initialize_bg_thread.kill.join
25
+ end
26
+ @parser = Parser.new
27
+ end
28
+
29
+ def self.initialize_async
30
+ if !@initialize_bg_thread.nil? && @initialize_bg_thread.alive?
31
+ @initialize_bg_thread.kill.join
32
+ end
33
+ @initialize_bg_thread = Thread.new { @parser = Parser.new }
34
+ @initialize_bg_thread
35
+ end
36
+
37
+ def self.parse_os(*args)
38
+ if @parser.nil?
39
+ initialize
40
+ end
41
+ @parser.parse_os(*args)
42
+ end
43
+
44
+ def self.parse_ua(*args)
45
+ if @parser.nil?
46
+ initialize
47
+ end
48
+ @parser.parse_ua(*args)
49
+ end
50
+
51
+ def self.parse_device(*args)
52
+ if @parser.nil?
53
+ initialize
54
+ end
55
+ @parser.parse_device(*args)
56
+ end
57
+ 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: 1.24.5
4
+ version: 1.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Statsig, Inc
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-03 00:00:00.000000000 Z
11
+ date: 2023-05-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '5.14'
47
+ version: 5.14.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '5.14'
54
+ version: 5.14.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: spy
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -123,19 +123,89 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: '6.0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: user_agent_parser
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.28.2
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.28.2
139
+ - !ruby/object:Gem::Dependency
140
+ name: parallel_tests
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - "~>"
130
144
  - !ruby/object:Gem::Version
131
145
  version: '2.7'
132
- type: :runtime
146
+ type: :development
133
147
  prerelease: false
134
148
  version_requirements: !ruby/object:Gem::Requirement
135
149
  requirements:
136
150
  - - "~>"
137
151
  - !ruby/object:Gem::Version
138
152
  version: '2.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.21'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.21'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov-lcov
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: 0.7.0
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 0.7.0
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov-cobertura
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '2.1'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '2.1'
195
+ - !ruby/object:Gem::Dependency
196
+ name: user_agent_parser
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: 2.15.0
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: 2.15.0
139
209
  - !ruby/object:Gem::Dependency
140
210
  name: http
141
211
  requirement: !ruby/object:Gem::Requirement
@@ -160,16 +230,16 @@ dependencies:
160
230
  name: ip3country
161
231
  requirement: !ruby/object:Gem::Requirement
162
232
  requirements:
163
- - - '='
233
+ - - "~>"
164
234
  - !ruby/object:Gem::Version
165
- version: 0.1.1
235
+ version: 0.2.1
166
236
  type: :runtime
167
237
  prerelease: false
168
238
  version_requirements: !ruby/object:Gem::Requirement
169
239
  requirements:
170
- - - '='
240
+ - - "~>"
171
241
  - !ruby/object:Gem::Version
172
- version: 0.1.1
242
+ version: 0.2.1
173
243
  - !ruby/object:Gem::Dependency
174
244
  name: sorbet-runtime
175
245
  requirement: !ruby/object:Gem::Requirement
@@ -224,6 +294,7 @@ files:
224
294
  - lib/statsig_logger.rb
225
295
  - lib/statsig_options.rb
226
296
  - lib/statsig_user.rb
297
+ - lib/ua_parser.rb
227
298
  homepage: https://rubygems.org/gems/statsig
228
299
  licenses:
229
300
  - ISC