airbrake-ruby 4.8.0 → 5.2.0

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +132 -57
  3. data/lib/airbrake-ruby/async_sender.rb +7 -30
  4. data/lib/airbrake-ruby/backtrace.rb +8 -7
  5. data/lib/airbrake-ruby/benchmark.rb +1 -1
  6. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  7. data/lib/airbrake-ruby/config.rb +59 -15
  8. data/lib/airbrake-ruby/config/processor.rb +71 -0
  9. data/lib/airbrake-ruby/config/validator.rb +9 -3
  10. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  11. data/lib/airbrake-ruby/file_cache.rb +1 -1
  12. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  13. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  14. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  15. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  16. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  17. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  18. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  19. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  21. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  22. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  23. data/lib/airbrake-ruby/filters/sql_filter.rb +7 -7
  24. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  25. data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
  26. data/lib/airbrake-ruby/grouppable.rb +12 -0
  27. data/lib/airbrake-ruby/ignorable.rb +1 -0
  28. data/lib/airbrake-ruby/inspectable.rb +2 -2
  29. data/lib/airbrake-ruby/loggable.rb +1 -1
  30. data/lib/airbrake-ruby/mergeable.rb +12 -0
  31. data/lib/airbrake-ruby/monotonic_time.rb +5 -0
  32. data/lib/airbrake-ruby/notice.rb +7 -14
  33. data/lib/airbrake-ruby/notice_notifier.rb +11 -3
  34. data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
  35. data/lib/airbrake-ruby/performance_notifier.rb +80 -58
  36. data/lib/airbrake-ruby/promise.rb +1 -0
  37. data/lib/airbrake-ruby/query.rb +20 -15
  38. data/lib/airbrake-ruby/queue.rb +65 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +105 -0
  40. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  41. data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
  42. data/lib/airbrake-ruby/request.rb +14 -12
  43. data/lib/airbrake-ruby/stat.rb +26 -33
  44. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  45. data/lib/airbrake-ruby/tdigest.rb +43 -58
  46. data/lib/airbrake-ruby/thread_pool.rb +11 -1
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/spec/airbrake_spec.rb +206 -71
  50. data/spec/async_sender_spec.rb +3 -12
  51. data/spec/backtrace_spec.rb +44 -44
  52. data/spec/code_hunk_spec.rb +11 -11
  53. data/spec/config/processor_spec.rb +143 -0
  54. data/spec/config/validator_spec.rb +23 -6
  55. data/spec/config_spec.rb +40 -14
  56. data/spec/deploy_notifier_spec.rb +2 -2
  57. data/spec/filter_chain_spec.rb +28 -1
  58. data/spec/filters/dependency_filter_spec.rb +1 -1
  59. data/spec/filters/gem_root_filter_spec.rb +9 -9
  60. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  61. data/spec/filters/git_repository_filter.rb +1 -1
  62. data/spec/filters/git_revision_filter_spec.rb +10 -10
  63. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  64. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  65. data/spec/filters/root_directory_filter_spec.rb +9 -9
  66. data/spec/filters/sql_filter_spec.rb +58 -60
  67. data/spec/filters/system_exit_filter_spec.rb +1 -1
  68. data/spec/filters/thread_filter_spec.rb +32 -30
  69. data/spec/fixtures/project_root/code.rb +9 -9
  70. data/spec/loggable_spec.rb +17 -0
  71. data/spec/monotonic_time_spec.rb +11 -0
  72. data/spec/notice_notifier/options_spec.rb +17 -17
  73. data/spec/notice_notifier_spec.rb +20 -20
  74. data/spec/notice_spec.rb +6 -6
  75. data/spec/performance_breakdown_spec.rb +0 -1
  76. data/spec/performance_notifier_spec.rb +220 -73
  77. data/spec/query_spec.rb +1 -1
  78. data/spec/queue_spec.rb +18 -0
  79. data/spec/remote_settings/callback_spec.rb +143 -0
  80. data/spec/remote_settings/settings_data_spec.rb +348 -0
  81. data/spec/remote_settings_spec.rb +187 -0
  82. data/spec/request_spec.rb +1 -3
  83. data/spec/response_spec.rb +8 -8
  84. data/spec/spec_helper.rb +6 -6
  85. data/spec/stat_spec.rb +2 -12
  86. data/spec/sync_sender_spec.rb +14 -12
  87. data/spec/tdigest_spec.rb +7 -7
  88. data/spec/thread_pool_spec.rb +39 -10
  89. data/spec/timed_trace_spec.rb +1 -1
  90. data/spec/truncator_spec.rb +12 -12
  91. metadata +32 -14
@@ -0,0 +1,44 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # Callback is a class that provides a callback for the config poller, which
4
+ # updates the local config according to the data.
5
+ #
6
+ # @api private
7
+ # @since v5.0.2
8
+ class Callback
9
+ def initialize(config)
10
+ @config = config
11
+ @orig_error_notifications = config.error_notifications
12
+ @orig_performance_stats = config.performance_stats
13
+ end
14
+
15
+ # @param [Airbrake::RemoteSettings::SettingsData] data
16
+ # @return [void]
17
+ def call(data)
18
+ @config.logger.debug do
19
+ "#{LOG_LABEL} applying remote settings: #{data.to_h}"
20
+ end
21
+
22
+ @config.error_host = data.error_host if data.error_host
23
+ @config.apm_host = data.apm_host if data.apm_host
24
+
25
+ process_error_notifications(data)
26
+ process_performance_stats(data)
27
+ end
28
+
29
+ private
30
+
31
+ def process_error_notifications(data)
32
+ return unless @orig_error_notifications
33
+
34
+ @config.error_notifications = data.error_notifications?
35
+ end
36
+
37
+ def process_performance_stats(data)
38
+ return unless @orig_performance_stats
39
+
40
+ @config.performance_stats = data.performance_stats?
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,116 @@
1
+ module Airbrake
2
+ class RemoteSettings
3
+ # SettingsData is a container, which wraps JSON payload returned by the
4
+ # remote settings API. It exposes the payload via convenient methods and
5
+ # also ensures that in case some data from the payload is missing, a default
6
+ # value would be returned instead.
7
+ #
8
+ # @example
9
+ # # Create the object and pass initial data (empty hash).
10
+ # settings_data = SettingsData.new({})
11
+ #
12
+ # settings_data.interval #=> 600
13
+ #
14
+ # @since v5.0.0
15
+ # @api private
16
+ class SettingsData
17
+ # @return [Integer] how frequently we should poll the config API
18
+ DEFAULT_INTERVAL = 600
19
+
20
+ # @return [String] API version of the S3 API to poll
21
+ API_VER = '2020-06-18'.freeze
22
+
23
+ # @return [String] what path to poll
24
+ CONFIG_ROUTE_PATTERN =
25
+ "%<host>s/#{API_VER}/config/%<project_id>s/config.json".freeze
26
+
27
+ # @return [Hash{Symbol=>String}] the hash of all supported settings where
28
+ # the value is the name of the setting returned by the API
29
+ SETTINGS = {
30
+ errors: 'errors'.freeze,
31
+ apm: 'apm'.freeze,
32
+ }.freeze
33
+
34
+ # @param [Integer] project_id
35
+ # @param [Hash{String=>Object}] data
36
+ def initialize(project_id, data)
37
+ @project_id = project_id
38
+ @data = data
39
+ end
40
+
41
+ # Merges the given +hash+ with internal data.
42
+ #
43
+ # @param [Hash{String=>Object}] hash
44
+ # @return [self]
45
+ def merge!(hash)
46
+ @data.merge!(hash)
47
+
48
+ self
49
+ end
50
+
51
+ # @return [Integer] how frequently we should poll for the config
52
+ def interval
53
+ return DEFAULT_INTERVAL if !@data.key?('poll_sec') || !@data['poll_sec']
54
+
55
+ @data['poll_sec'] > 0 ? @data['poll_sec'] : DEFAULT_INTERVAL
56
+ end
57
+
58
+ # @param [String] remote_config_host
59
+ # @return [String] where the config is stored on S3.
60
+ def config_route(remote_config_host)
61
+ if @data['config_route'] && !@data['config_route'].empty?
62
+ return remote_config_host.chomp('/') + '/' + @data['config_route']
63
+ end
64
+
65
+ format(
66
+ CONFIG_ROUTE_PATTERN,
67
+ host: remote_config_host.chomp('/'),
68
+ project_id: @project_id,
69
+ )
70
+ end
71
+
72
+ # @return [Boolean] whether error notifications are enabled
73
+ def error_notifications?
74
+ return true unless (s = find_setting(SETTINGS[:errors]))
75
+
76
+ s['enabled']
77
+ end
78
+
79
+ # @return [Boolean] whether APM is enabled
80
+ def performance_stats?
81
+ return true unless (s = find_setting(SETTINGS[:apm]))
82
+
83
+ s['enabled']
84
+ end
85
+
86
+ # @return [String, nil] the host, which provides the API endpoint to which
87
+ # exceptions should be sent
88
+ def error_host
89
+ return unless (s = find_setting(SETTINGS[:errors]))
90
+
91
+ s['endpoint']
92
+ end
93
+
94
+ # @return [String, nil] the host, which provides the API endpoint to which
95
+ # APM data should be sent
96
+ def apm_host
97
+ return unless (s = find_setting(SETTINGS[:apm]))
98
+
99
+ s['endpoint']
100
+ end
101
+
102
+ # @return [Hash{String=>Object}] raw representation of JSON payload
103
+ def to_h
104
+ @data.dup
105
+ end
106
+
107
+ private
108
+
109
+ def find_setting(name)
110
+ return unless @data.key?('settings')
111
+
112
+ @data['settings'].find { |s| s['name'] == name }
113
+ end
114
+ end
115
+ end
116
+ end
@@ -4,21 +4,28 @@ module Airbrake
4
4
  # @see Airbrake.notify_request
5
5
  # @api public
6
6
  # @since v3.2.0
7
- # rubocop:disable Metrics/BlockLength
8
- Request = Struct.new(:method, :route, :status_code, :start_time, :end_time) do
7
+ class Request
9
8
  include HashKeyable
10
9
  include Ignorable
11
10
  include Stashable
11
+ include Mergeable
12
+ include Grouppable
13
+
14
+ attr_accessor :method, :route, :status_code, :timing, :time
12
15
 
13
16
  def initialize(
14
17
  method:,
15
18
  route:,
16
19
  status_code:,
17
- start_time:,
18
- end_time: Time.now
20
+ timing: nil,
21
+ time: Time.now
19
22
  )
20
- @start_time_utc = TimeTruncate.utc_truncate_minutes(start_time)
21
- super(method, route, status_code, start_time, end_time)
23
+ @time_utc = TimeTruncate.utc_truncate_minutes(time)
24
+ @method = method
25
+ @route = route
26
+ @status_code = status_code
27
+ @timing = timing
28
+ @time = time
22
29
  end
23
30
 
24
31
  def destination
@@ -29,18 +36,13 @@ module Airbrake
29
36
  'routes'
30
37
  end
31
38
 
32
- def groups
33
- {}
34
- end
35
-
36
39
  def to_h
37
40
  {
38
41
  'method' => method,
39
42
  'route' => route,
40
43
  'statusCode' => status_code,
41
- 'time' => @start_time_utc
44
+ 'time' => @time_utc,
42
45
  }.delete_if { |_key, val| val.nil? }
43
46
  end
44
47
  end
45
- # rubocop:enable Metrics/BlockLength
46
48
  end
@@ -1,6 +1,5 @@
1
1
  require 'base64'
2
2
 
3
- # rubocop:disable Metrics/BlockLength
4
3
  module Airbrake
5
4
  # Stat is a data structure that allows accumulating performance data (route
6
5
  # performance, SQL query performance and such). It's powered by TDigests.
@@ -10,54 +9,49 @@ module Airbrake
10
9
  #
11
10
  # @example
12
11
  # stat = Airbrake::Stat.new
13
- # stat.increment(Time.now - 200)
12
+ # stat.increment_ms(2000)
14
13
  # stat.to_h # Pack and serialize data so it can be transmitted.
15
14
  #
16
15
  # @since v3.2.0
17
- Stat = Struct.new(:count, :sum, :sumsq, :tdigest) do
18
- # @param [Integer] count How many times this stat was incremented
16
+ class Stat
17
+ attr_accessor :sum, :sumsq, :tdigest
18
+
19
19
  # @param [Float] sum The sum of duration in milliseconds
20
20
  # @param [Float] sumsq The squared sum of duration in milliseconds
21
21
  # @param [TDigest::TDigest] tdigest Packed durations. By default,
22
22
  # compression is 20
23
- def initialize(count: 0, sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
24
- super(count, sum, sumsq, tdigest)
23
+ def initialize(sum: 0.0, sumsq: 0.0, tdigest: TDigest.new(0.05))
24
+ @sum = sum
25
+ @sumsq = sumsq
26
+ @tdigest = tdigest
27
+ @mutex = Mutex.new
25
28
  end
26
29
 
27
30
  # @return [Hash{String=>Object}] stats as a hash with compressed TDigest
28
31
  # (serialized as base64)
29
32
  def to_h
30
- tdigest.compress!
31
- {
32
- 'count' => count,
33
- 'sum' => sum,
34
- 'sumsq' => sumsq,
35
- 'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes)
36
- }
37
- end
38
-
39
- # Increments count and updates performance with the difference of +end_time+
40
- # and +start_time+.
41
- #
42
- # @param [Date] start_time
43
- # @param [Date] end_time
44
- # @return [void]
45
- def increment(start_time, end_time = nil)
46
- end_time ||= Time.new
47
- increment_ms((end_time - start_time) * 1000)
33
+ @mutex.synchronize do
34
+ tdigest.compress!
35
+ {
36
+ 'count' => tdigest.size,
37
+ 'sum' => sum,
38
+ 'sumsq' => sumsq,
39
+ 'tdigest' => Base64.strict_encode64(tdigest.as_small_bytes),
40
+ }
41
+ end
48
42
  end
49
43
 
50
- # Increments count and updates performance with given +ms+ value.
44
+ # Increments tdigest timings and updates tdigest with given +ms+ value.
51
45
  #
52
46
  # @param [Float] ms
53
47
  # @return [void]
54
48
  def increment_ms(ms)
55
- self.count += 1
56
-
57
- self.sum += ms
58
- self.sumsq += ms * ms
49
+ @mutex.synchronize do
50
+ self.sum += ms
51
+ self.sumsq += ms * ms
59
52
 
60
- tdigest.push(ms)
53
+ tdigest.push(ms)
54
+ end
61
55
  end
62
56
 
63
57
  # We define custom inspect so that we weed out uninformative TDigest, which
@@ -65,9 +59,8 @@ module Airbrake
65
59
  #
66
60
  # @return [String]
67
61
  def inspect
68
- "#<struct Airbrake::Stat count=#{count}, sum=#{sum}, sumsq=#{sumsq}>"
62
+ "#<struct Airbrake::Stat count=#{tdigest.size}, sum=#{sum}, sumsq=#{sumsq}>"
69
63
  end
70
- alias_method :pretty_print, :inspect
64
+ alias pretty_print inspect
71
65
  end
72
66
  end
73
- # rubocop:enable Metrics/BlockLength
@@ -23,7 +23,7 @@ module Airbrake
23
23
  # @param [#to_json] data
24
24
  # @param [URI::HTTPS] endpoint
25
25
  # @return [Hash{String=>String}] the parsed HTTP response
26
- def send(data, promise, endpoint = @config.endpoint)
26
+ def send(data, promise, endpoint = @config.error_endpoint)
27
27
  return promise if rate_limited_ip?(promise)
28
28
 
29
29
  response = nil
@@ -47,6 +47,7 @@ module Airbrake
47
47
  end
48
48
 
49
49
  return promise.reject(parsed_resp['error']) if parsed_resp.key?('error')
50
+
50
51
  promise.resolve(parsed_resp)
51
52
  end
52
53
 
@@ -79,7 +80,7 @@ module Airbrake
79
80
  req['Authorization'] = "Bearer #{@config.project_key}"
80
81
  req['Content-Type'] = CONTENT_TYPE
81
82
  req['User-Agent'] =
82
- "#{Airbrake::Notice::NOTIFIER[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
83
+ "#{Airbrake::NOTIFIER_INFO[:name]}/#{Airbrake::AIRBRAKE_RUBY_VERSION}" \
83
84
  " Ruby/#{RUBY_VERSION}"
84
85
 
85
86
  req
@@ -37,14 +37,15 @@ module Airbrake
37
37
  end
38
38
 
39
39
  attr_accessor :centroids
40
+ attr_reader :size
41
+
40
42
  def initialize(delta = 0.01, k = 25, cx = 1.1)
41
43
  @delta = delta
42
44
  @k = k
43
45
  @cx = cx
44
46
  @centroids = RBTree.new
45
- @nreset = 0
46
- @n = 0
47
- reset!
47
+ @size = 0
48
+ @last_cumulate = 0
48
49
  end
49
50
 
50
51
  def +(other)
@@ -59,8 +60,8 @@ module Airbrake
59
60
  # compression as defined by Java implementation
60
61
  size = @centroids.size
61
62
  output = [VERBOSE_ENCODING, compression, size]
62
- output += @centroids.map { |_, c| c.mean }
63
- output += @centroids.map { |_, c| c.n }
63
+ output += @centroids.each_value.map(&:mean)
64
+ output += @centroids.each_value.map(&:n)
64
65
  output.pack("NGNG#{size}N#{size}")
65
66
  end
66
67
 
@@ -70,14 +71,14 @@ module Airbrake
70
71
  output = [self.class::SMALL_ENCODING, compression, size]
71
72
  x = 0
72
73
  # delta encoding allows saving 4-bytes floats
73
- mean_arr = @centroids.map do |_, c|
74
+ mean_arr = @centroids.each_value.map do |c|
74
75
  val = c.mean - x
75
76
  x = c.mean
76
77
  val
77
78
  end
78
79
  output += mean_arr
79
80
  # Variable length encoding of numbers
80
- c_arr = @centroids.each_with_object([]) do |(_, c), arr|
81
+ c_arr = @centroids.each_value.each_with_object([]) do |c, arr|
81
82
  k = 0
82
83
  n = c.n
83
84
  while n < 0 || n > 0x7f
@@ -95,7 +96,7 @@ module Airbrake
95
96
  # rubocop:enable Metrics/AbcSize
96
97
 
97
98
  def as_json(_ = nil)
98
- @centroids.map { |_, c| c.as_json }
99
+ @centroids.each_value.map(&:as_json)
99
100
  end
100
101
 
101
102
  def bound_mean(x)
@@ -138,21 +139,17 @@ module Airbrake
138
139
  end
139
140
 
140
141
  def find_nearest(x)
141
- return nil if size == 0
142
-
143
- ceil = @centroids.upper_bound(x)
144
- floor = @centroids.lower_bound(x)
145
-
146
- return floor[1] if ceil.nil?
147
- return ceil[1] if floor.nil?
142
+ return if size == 0
148
143
 
149
- ceil_key = ceil[0]
150
- floor_key = floor[0]
144
+ upper_key, upper = @centroids.upper_bound(x)
145
+ lower_key, lower = @centroids.lower_bound(x)
146
+ return lower unless upper_key
147
+ return upper unless lower_key
151
148
 
152
- if (floor_key - x).abs < (ceil_key - x).abs
153
- floor[1]
149
+ if (lower_key - x).abs < (upper_key - x).abs
150
+ lower
154
151
  else
155
- ceil[1]
152
+ upper
156
153
  end
157
154
  end
158
155
 
@@ -186,7 +183,7 @@ module Airbrake
186
183
  mean_cumn += (item - lower.mean) * (upper.mean_cumn - lower.mean_cumn) \
187
184
  / (upper.mean - lower.mean)
188
185
  end
189
- mean_cumn / @n
186
+ mean_cumn / @size
190
187
  end
191
188
  end
192
189
  is_array ? x : x.first
@@ -203,11 +200,12 @@ module Airbrake
203
200
  unless (0..1).cover?(item)
204
201
  raise ArgumentError, "p should be in [0,1], got #{item}"
205
202
  end
203
+
206
204
  if size == 0
207
205
  nil
208
206
  else
209
207
  _cumulate(true)
210
- h = @n * item
208
+ h = @size * item
211
209
  lower, upper = bound_mean_cumn(h)
212
210
  if lower.nil? && upper.nil?
213
211
  nil
@@ -237,17 +235,12 @@ module Airbrake
237
235
 
238
236
  def reset!
239
237
  @centroids.clear
240
- @n = 0
241
- @nreset += 1
238
+ @size = 0
242
239
  @last_cumulate = 0
243
240
  end
244
241
 
245
- def size
246
- @n || 0
247
- end
248
-
249
242
  def to_a
250
- @centroids.map { |_, c| c }
243
+ @centroids.each_value.to_a
251
244
  end
252
245
 
253
246
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/MethodLength
@@ -279,6 +272,7 @@ module Airbrake
279
272
  shift = 7
280
273
  while (v & 0x80) != 0
281
274
  raise 'Shift too large in decode' if shift > 28
275
+
282
276
  v = counts_bytes.shift || 0
283
277
  z += (v & 0x7f) << shift
284
278
  shift += 7
@@ -307,16 +301,16 @@ module Airbrake
307
301
 
308
302
  private
309
303
 
310
- def _add_weight(nearest, x, n)
311
- nearest.mean += n * (x - nearest.mean) / (nearest.n + n) unless x == nearest.mean
312
-
313
- _cumulate(false, true) if nearest.mean_cumn.nil?
304
+ def _add_weight(centroid, x, n)
305
+ unless x == centroid.mean
306
+ centroid.mean += n * (x - centroid.mean) / (centroid.n + n)
307
+ end
314
308
 
315
- nearest.cumn += n
316
- nearest.mean_cumn += n / 2.0
317
- nearest.n += n
309
+ _cumulate(false, true) if centroid.mean_cumn.nil?
318
310
 
319
- nil
311
+ centroid.cumn += n
312
+ centroid.mean_cumn += n / 2.0
313
+ centroid.n += n
320
314
  end
321
315
 
322
316
  # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -325,17 +319,17 @@ module Airbrake
325
319
  factor = if @last_cumulate == 0
326
320
  Float::INFINITY
327
321
  else
328
- (@n.to_f / @last_cumulate)
322
+ (@size.to_f / @last_cumulate)
329
323
  end
330
- return if @n == @last_cumulate || (!exact && @cx && @cx > factor)
324
+ return if @size == @last_cumulate || (!exact && @cx && @cx > factor)
331
325
  end
332
326
 
333
327
  cumn = 0
334
- @centroids.each do |_, c|
328
+ @centroids.each_value do |c|
335
329
  c.mean_cumn = cumn + c.n / 2.0
336
330
  cumn = c.cumn = cumn + c.n
337
331
  end
338
- @n = @last_cumulate = cumn
332
+ @size = @last_cumulate = cumn
339
333
  nil
340
334
  end
341
335
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
@@ -345,28 +339,25 @@ module Airbrake
345
339
  def _digest(x, n)
346
340
  # Use 'first' and 'last' instead of min/max because of performance reasons
347
341
  # This works because RBTree is sorted
348
- min = @centroids.first
349
- max = @centroids.last
350
-
351
- min = min.nil? ? nil : min[1]
352
- max = max.nil? ? nil : max[1]
342
+ min = min.last if (min = @centroids.first)
343
+ max = max.last if (max = @centroids.last)
353
344
  nearest = find_nearest(x)
354
345
 
355
- @n += n
346
+ @size += n
356
347
 
357
348
  if nearest && nearest.mean == x
358
349
  _add_weight(nearest, x, n)
359
350
  elsif nearest == min
360
- _new_centroid(x, n, 0)
351
+ @centroids[x] = Centroid.new(x, n, 0)
361
352
  elsif nearest == max
362
- _new_centroid(x, n, @n)
353
+ @centroids[x] = Centroid.new(x, n, @size)
363
354
  else
364
- p = nearest.mean_cumn.to_f / @n
365
- max_n = (4 * @n * @delta * p * (1 - p)).floor
355
+ p = nearest.mean_cumn.to_f / @size
356
+ max_n = (4 * @size * @delta * p * (1 - p)).floor
366
357
  if max_n - nearest.n >= n
367
358
  _add_weight(nearest, x, n)
368
359
  else
369
- _new_centroid(x, n, nearest.cumn)
360
+ @centroids[x] = Centroid.new(x, n, nearest.cumn)
370
361
  end
371
362
  end
372
363
 
@@ -382,12 +373,6 @@ module Airbrake
382
373
  end
383
374
  # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity,
384
375
  # rubocop:enable Metrics/AbcSize
385
-
386
- def _new_centroid(x, n, cumn)
387
- c = Centroid.new(x, n, cumn)
388
- @centroids[x] = c
389
- c
390
- end
391
376
  end
392
377
  # rubocop:enable Metrics/ClassLength
393
378
  end