airbrake-ruby 4.8.0 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|