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.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +132 -57
- data/lib/airbrake-ruby/async_sender.rb +7 -30
- data/lib/airbrake-ruby/backtrace.rb +8 -7
- data/lib/airbrake-ruby/benchmark.rb +1 -1
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config.rb +59 -15
- data/lib/airbrake-ruby/config/processor.rb +71 -0
- data/lib/airbrake-ruby/config/validator.rb +9 -3
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/file_cache.rb +1 -1
- data/lib/airbrake-ruby/filter_chain.rb +16 -1
- data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
- data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
- data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
- data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
- data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
- data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
- data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/sql_filter.rb +7 -7
- data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
- data/lib/airbrake-ruby/filters/thread_filter.rb +5 -4
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/ignorable.rb +1 -0
- data/lib/airbrake-ruby/inspectable.rb +2 -2
- data/lib/airbrake-ruby/loggable.rb +1 -1
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/monotonic_time.rb +5 -0
- data/lib/airbrake-ruby/notice.rb +7 -14
- data/lib/airbrake-ruby/notice_notifier.rb +11 -3
- data/lib/airbrake-ruby/performance_breakdown.rb +16 -10
- data/lib/airbrake-ruby/performance_notifier.rb +80 -58
- data/lib/airbrake-ruby/promise.rb +1 -0
- data/lib/airbrake-ruby/query.rb +20 -15
- data/lib/airbrake-ruby/queue.rb +65 -0
- data/lib/airbrake-ruby/remote_settings.rb +105 -0
- data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
- data/lib/airbrake-ruby/remote_settings/settings_data.rb +116 -0
- data/lib/airbrake-ruby/request.rb +14 -12
- data/lib/airbrake-ruby/stat.rb +26 -33
- data/lib/airbrake-ruby/sync_sender.rb +3 -2
- data/lib/airbrake-ruby/tdigest.rb +43 -58
- data/lib/airbrake-ruby/thread_pool.rb +11 -1
- data/lib/airbrake-ruby/truncator.rb +10 -4
- data/lib/airbrake-ruby/version.rb +11 -1
- data/spec/airbrake_spec.rb +206 -71
- data/spec/async_sender_spec.rb +3 -12
- data/spec/backtrace_spec.rb +44 -44
- data/spec/code_hunk_spec.rb +11 -11
- data/spec/config/processor_spec.rb +143 -0
- data/spec/config/validator_spec.rb +23 -6
- data/spec/config_spec.rb +40 -14
- data/spec/deploy_notifier_spec.rb +2 -2
- data/spec/filter_chain_spec.rb +28 -1
- data/spec/filters/dependency_filter_spec.rb +1 -1
- data/spec/filters/gem_root_filter_spec.rb +9 -9
- data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
- data/spec/filters/git_repository_filter.rb +1 -1
- data/spec/filters/git_revision_filter_spec.rb +10 -10
- data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
- data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
- data/spec/filters/root_directory_filter_spec.rb +9 -9
- data/spec/filters/sql_filter_spec.rb +58 -60
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +32 -30
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/loggable_spec.rb +17 -0
- data/spec/monotonic_time_spec.rb +11 -0
- data/spec/notice_notifier/options_spec.rb +17 -17
- data/spec/notice_notifier_spec.rb +20 -20
- data/spec/notice_spec.rb +6 -6
- data/spec/performance_breakdown_spec.rb +0 -1
- data/spec/performance_notifier_spec.rb +220 -73
- data/spec/query_spec.rb +1 -1
- data/spec/queue_spec.rb +18 -0
- data/spec/remote_settings/callback_spec.rb +143 -0
- data/spec/remote_settings/settings_data_spec.rb +348 -0
- data/spec/remote_settings_spec.rb +187 -0
- data/spec/request_spec.rb +1 -3
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +6 -6
- data/spec/stat_spec.rb +2 -12
- data/spec/sync_sender_spec.rb +14 -12
- data/spec/tdigest_spec.rb +7 -7
- data/spec/thread_pool_spec.rb +39 -10
- data/spec/timed_trace_spec.rb +1 -1
- data/spec/truncator_spec.rb +12 -12
- 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
|
-
|
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
|
-
|
18
|
-
|
20
|
+
timing: nil,
|
21
|
+
time: Time.now
|
19
22
|
)
|
20
|
-
@
|
21
|
-
|
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' => @
|
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
|
data/lib/airbrake-ruby/stat.rb
CHANGED
@@ -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.
|
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
|
18
|
-
|
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(
|
24
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
self.sumsq += ms * ms
|
49
|
+
@mutex.synchronize do
|
50
|
+
self.sum += ms
|
51
|
+
self.sumsq += ms * ms
|
59
52
|
|
60
|
-
|
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=#{
|
62
|
+
"#<struct Airbrake::Stat count=#{tdigest.size}, sum=#{sum}, sumsq=#{sumsq}>"
|
69
63
|
end
|
70
|
-
|
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.
|
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::
|
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
|
-
@
|
46
|
-
@
|
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
|
63
|
-
output += @centroids.map
|
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 |
|
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 |
|
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
|
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
|
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
|
-
|
150
|
-
|
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 (
|
153
|
-
|
149
|
+
if (lower_key - x).abs < (upper_key - x).abs
|
150
|
+
lower
|
154
151
|
else
|
155
|
-
|
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 / @
|
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 = @
|
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
|
-
@
|
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.
|
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(
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
316
|
-
nearest.mean_cumn += n / 2.0
|
317
|
-
nearest.n += n
|
309
|
+
_cumulate(false, true) if centroid.mean_cumn.nil?
|
318
310
|
|
319
|
-
|
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
|
-
(@
|
322
|
+
(@size.to_f / @last_cumulate)
|
329
323
|
end
|
330
|
-
return if @
|
324
|
+
return if @size == @last_cumulate || (!exact && @cx && @cx > factor)
|
331
325
|
end
|
332
326
|
|
333
327
|
cumn = 0
|
334
|
-
@centroids.
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
351
|
+
@centroids[x] = Centroid.new(x, n, 0)
|
361
352
|
elsif nearest == max
|
362
|
-
|
353
|
+
@centroids[x] = Centroid.new(x, n, @size)
|
363
354
|
else
|
364
|
-
p = nearest.mean_cumn.to_f / @
|
365
|
-
max_n = (4 * @
|
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
|
-
|
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
|