airbrake-ruby 4.1.0 → 4.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 +25 -1
- data/lib/airbrake-ruby/performance_breakdown.rb +44 -0
- data/lib/airbrake-ruby/performance_notifier.rb +48 -21
- data/lib/airbrake-ruby/query.rb +9 -1
- data/lib/airbrake-ruby/request.rb +11 -1
- data/lib/airbrake-ruby/stat.rb +7 -1
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/notice_notifier_spec.rb +0 -4
- data/spec/notice_notifier_spec/options_spec.rb +0 -4
- data/spec/performance_notifier_spec.rb +95 -3
- data/spec/spec_helper.rb +3 -0
- data/spec/stat_spec.rb +8 -10
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da152828a1f9be5664faef3a2bce8cb09755ea4f
|
4
|
+
data.tar.gz: 3db1f8a4dc6f496c1067d7d0e295054ee6c8fce2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cbf759c4bfb802574e23176ae0629b46bc4c7ddf73221477aa3e5b4af6dae02b4ca7d4b7e76984486e3b5b8f4d0a1d997bbf32c46d8076054555c83851160cfa
|
7
|
+
data.tar.gz: 3055d714f7d8263efd11e9de8748c04bb84031382fb1e377eef2333c2712636b48d774186a5f0b6d092de9d1fa73d43446dbf06f5b41e4587acf94a7cb05a92a
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -46,6 +46,7 @@ require 'airbrake-ruby/time_truncate'
|
|
46
46
|
require 'airbrake-ruby/tdigest'
|
47
47
|
require 'airbrake-ruby/query'
|
48
48
|
require 'airbrake-ruby/request'
|
49
|
+
require 'airbrake-ruby/performance_breakdown'
|
49
50
|
|
50
51
|
# Airbrake is a thin wrapper around instances of the notifier classes (such as
|
51
52
|
# notice, performance & deploy notifiers). It creates a way to access them via a
|
@@ -352,7 +353,6 @@ module Airbrake
|
|
352
353
|
# )
|
353
354
|
#
|
354
355
|
# @param [Hash{Symbol=>Object}] query_info
|
355
|
-
# @option request_info [String] :environment (optional)
|
356
356
|
# @option request_info [String] :method The HTTP method that triggered this
|
357
357
|
# SQL query (optional)
|
358
358
|
# @option request_info [String] :route The route that triggered this SQL
|
@@ -367,6 +367,30 @@ module Airbrake
|
|
367
367
|
@performance_notifier.notify(Query.new(query_info))
|
368
368
|
end
|
369
369
|
|
370
|
+
# Increments performance breakdown statistics of a certain route.
|
371
|
+
#
|
372
|
+
# @example
|
373
|
+
# Airbrake.notify_request(
|
374
|
+
# method: 'POST',
|
375
|
+
# route: '/thing/:id/create',
|
376
|
+
# response_type: 'json',
|
377
|
+
# groups: { db: 24.0, view: 0.4 }, # ms
|
378
|
+
# start_time: timestamp,
|
379
|
+
# end_time: Time.now
|
380
|
+
# )
|
381
|
+
#
|
382
|
+
# @param [Hash{Symbol=>Object}] breakdown_info
|
383
|
+
# @option breakdown_info [String] :method HTTP method
|
384
|
+
# @option breakdown_info [String] :route
|
385
|
+
# @option breakdown_info [String] :response_type
|
386
|
+
# @option breakdown_info [Array<Hash{Symbol=>Float}>] :groups
|
387
|
+
# @option breakdown_info [Date] :start_time
|
388
|
+
# @return [void]
|
389
|
+
# @since v4.2.0
|
390
|
+
def notify_performance_breakdown(breakdown_info)
|
391
|
+
@performance_notifier.notify(PerformanceBreakdown.new(breakdown_info))
|
392
|
+
end
|
393
|
+
|
370
394
|
# Runs a callback before {.notify_request} or {.notify_query} kicks in. This
|
371
395
|
# is useful if you want to ignore specific resources or filter the data the
|
372
396
|
# resource contains.
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# PerformanceBreakdown holds data that shows how much time a request spent
|
3
|
+
# doing certaing subtasks such as (DB querying, view rendering, etc). request
|
4
|
+
#
|
5
|
+
# @see Airbrake.notify_breakdown
|
6
|
+
# @api public
|
7
|
+
# @since v4.3.0
|
8
|
+
# rubocop:disable Metrics/BlockLength, Metrics/ParameterLists
|
9
|
+
PerformanceBreakdown = Struct.new(
|
10
|
+
:method, :route, :response_type, :groups, :start_time, :end_time
|
11
|
+
) do
|
12
|
+
include HashKeyable
|
13
|
+
include Ignorable
|
14
|
+
|
15
|
+
def initialize(
|
16
|
+
method:,
|
17
|
+
route:,
|
18
|
+
response_type:,
|
19
|
+
groups:,
|
20
|
+
start_time:,
|
21
|
+
end_time: Time.now
|
22
|
+
)
|
23
|
+
super(method, route, response_type, groups, start_time, end_time)
|
24
|
+
end
|
25
|
+
|
26
|
+
def destination
|
27
|
+
'routes-breakdowns'
|
28
|
+
end
|
29
|
+
|
30
|
+
def cargo
|
31
|
+
'routes'
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
{
|
36
|
+
'method' => method,
|
37
|
+
'route' => route,
|
38
|
+
'responseType' => response_type,
|
39
|
+
'time' => TimeTruncate.utc_truncate_minutes(start_time)
|
40
|
+
}.delete_if { |_key, val| val.nil? }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/BlockLength, Metrics/ParameterLists
|
44
|
+
end
|
@@ -34,14 +34,8 @@ module Airbrake
|
|
34
34
|
return if resource.ignored?
|
35
35
|
|
36
36
|
@mutex.synchronize do
|
37
|
-
|
38
|
-
@payload
|
39
|
-
|
40
|
-
if @flush_period > 0
|
41
|
-
schedule_flush(promise)
|
42
|
-
else
|
43
|
-
send(@payload, promise)
|
44
|
-
end
|
37
|
+
update_payload(resource)
|
38
|
+
@flush_period > 0 ? schedule_flush(promise) : send(@payload, promise)
|
45
39
|
end
|
46
40
|
|
47
41
|
promise
|
@@ -59,6 +53,16 @@ module Airbrake
|
|
59
53
|
|
60
54
|
private
|
61
55
|
|
56
|
+
def update_payload(resource)
|
57
|
+
@payload[resource] ||= { total: Airbrake::Stat.new }
|
58
|
+
@payload[resource][:total].increment(resource.start_time, resource.end_time)
|
59
|
+
|
60
|
+
resource.groups.each do |name, ms|
|
61
|
+
@payload[resource][name] ||= Airbrake::Stat.new
|
62
|
+
@payload[resource][name].increment_ms(ms)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
62
66
|
def schedule_flush(promise)
|
63
67
|
@schedule_flush ||= Thread.new do
|
64
68
|
sleep(@flush_period)
|
@@ -74,27 +78,50 @@ module Airbrake
|
|
74
78
|
end
|
75
79
|
end
|
76
80
|
|
77
|
-
# rubocop:disable Metrics/AbcSize
|
78
81
|
def send(payload, promise)
|
79
82
|
signature = "#{self.class.name}##{__method__}"
|
80
83
|
raise "#{signature}: payload (#{payload}) cannot be empty. Race?" if payload.none?
|
81
84
|
|
82
85
|
logger.debug("#{LOG_LABEL} #{signature}: #{payload}")
|
83
86
|
|
84
|
-
payload
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@sender.send(
|
89
|
-
data,
|
90
|
-
promise,
|
91
|
-
URI.join(
|
92
|
-
@config.host,
|
93
|
-
"api/v5/projects/#{@config.project_id}/#{resource_name}-stats"
|
94
|
-
)
|
87
|
+
with_grouped_payload(payload) do |resource_hash, destination|
|
88
|
+
url = URI.join(
|
89
|
+
@config.host,
|
90
|
+
"api/v5/projects/#{@config.project_id}/#{destination}"
|
95
91
|
)
|
92
|
+
@sender.send(resource_hash, promise, url)
|
93
|
+
end
|
94
|
+
|
95
|
+
promise
|
96
|
+
end
|
97
|
+
|
98
|
+
def with_grouped_payload(raw_payload)
|
99
|
+
grouped_payload = raw_payload.group_by do |resource, _stats|
|
100
|
+
[resource.cargo, resource.destination]
|
101
|
+
end
|
102
|
+
|
103
|
+
grouped_payload.each do |(cargo, destination), resources|
|
104
|
+
payload = {}
|
105
|
+
payload[cargo] = serialize_resources(resources)
|
106
|
+
payload['environment'] = @config.environment if @config.environment
|
107
|
+
|
108
|
+
yield(payload, destination)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def serialize_resources(resources)
|
113
|
+
resources.map do |resource, stats|
|
114
|
+
resource_hash = resource.to_h.merge!(stats[:total].to_h)
|
115
|
+
|
116
|
+
if resource.groups.any?
|
117
|
+
group_stats = stats.reject { |name, _stat| name == :total }
|
118
|
+
resource_hash['groups'] = group_stats.merge(group_stats) do |_name, stat|
|
119
|
+
stat.to_h
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
resource_hash
|
96
124
|
end
|
97
125
|
end
|
98
|
-
# rubocop:enable Metrics/AbcSize
|
99
126
|
end
|
100
127
|
end
|
data/lib/airbrake-ruby/query.rb
CHANGED
@@ -24,10 +24,18 @@ module Airbrake
|
|
24
24
|
super(method, route, query, func, file, line, start_time, end_time)
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def destination
|
28
|
+
'queries-stats'
|
29
|
+
end
|
30
|
+
|
31
|
+
def cargo
|
28
32
|
'queries'
|
29
33
|
end
|
30
34
|
|
35
|
+
def groups
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
31
39
|
def to_h
|
32
40
|
{
|
33
41
|
'method' => method,
|
@@ -4,6 +4,7 @@ module Airbrake
|
|
4
4
|
# @see Airbrake.notify_request
|
5
5
|
# @api public
|
6
6
|
# @since v3.2.0
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
7
8
|
Request = Struct.new(:method, :route, :status_code, :start_time, :end_time) do
|
8
9
|
include HashKeyable
|
9
10
|
include Ignorable
|
@@ -18,10 +19,18 @@ module Airbrake
|
|
18
19
|
super(method, route, status_code, start_time, end_time)
|
19
20
|
end
|
20
21
|
|
21
|
-
def
|
22
|
+
def destination
|
23
|
+
'routes-stats'
|
24
|
+
end
|
25
|
+
|
26
|
+
def cargo
|
22
27
|
'routes'
|
23
28
|
end
|
24
29
|
|
30
|
+
def groups
|
31
|
+
{}
|
32
|
+
end
|
33
|
+
|
25
34
|
def to_h
|
26
35
|
{
|
27
36
|
'method' => method,
|
@@ -31,4 +40,5 @@ module Airbrake
|
|
31
40
|
}.delete_if { |_key, val| val.nil? }
|
32
41
|
end
|
33
42
|
end
|
43
|
+
# rubocop:enable Metrics/BlockLength
|
34
44
|
end
|
data/lib/airbrake-ruby/stat.rb
CHANGED
@@ -43,10 +43,16 @@ module Airbrake
|
|
43
43
|
# @return [void]
|
44
44
|
def increment(start_time, end_time = nil)
|
45
45
|
end_time ||= Time.new
|
46
|
+
increment_ms((end_time - start_time) * 1000)
|
47
|
+
end
|
46
48
|
|
49
|
+
# Increments count and updates performance with given +ms+ value.
|
50
|
+
#
|
51
|
+
# @param [Float] ms
|
52
|
+
# @return [void]
|
53
|
+
def increment_ms(ms)
|
47
54
|
self.count += 1
|
48
55
|
|
49
|
-
ms = (end_time - start_time) * 1000
|
50
56
|
self.sum += ms
|
51
57
|
self.sumsq += ms * ms
|
52
58
|
|
@@ -185,10 +185,6 @@ RSpec.describe Airbrake::NoticeNotifier do
|
|
185
185
|
describe "#notify_sync" do
|
186
186
|
let(:endpoint) { 'https://api.airbrake.io/api/v3/projects/1/notices' }
|
187
187
|
|
188
|
-
let(:user_params) do
|
189
|
-
{ project_id: 1, project_key: 'abc', logger: Logger.new('/dev/null') }
|
190
|
-
end
|
191
|
-
|
192
188
|
let(:body) do
|
193
189
|
{
|
194
190
|
'id' => '00054414-b147-6ffa-85d6-1524d83362a6',
|
@@ -1,8 +1,4 @@
|
|
1
1
|
RSpec.describe Airbrake::NoticeNotifier do
|
2
|
-
def expect_a_request_with_body(body)
|
3
|
-
expect(a_request(:post, endpoint).with(body: body)).to have_been_made.once
|
4
|
-
end
|
5
|
-
|
6
2
|
let(:project_id) { 105138 }
|
7
3
|
let(:project_key) { 'fd04e13d806a90f96614ad8e529b2822' }
|
8
4
|
let(:localhost) { 'http://localhost:8080' }
|
@@ -1,10 +1,12 @@
|
|
1
1
|
RSpec.describe Airbrake::PerformanceNotifier do
|
2
2
|
let(:routes) { 'https://api.airbrake.io/api/v5/projects/1/routes-stats' }
|
3
3
|
let(:queries) { 'https://api.airbrake.io/api/v5/projects/1/queries-stats' }
|
4
|
+
let(:breakdowns) { 'https://api.airbrake.io/api/v5/projects/1/routes-breakdowns' }
|
4
5
|
|
5
6
|
before do
|
6
7
|
stub_request(:put, routes).to_return(status: 200, body: '')
|
7
8
|
stub_request(:put, queries).to_return(status: 200, body: '')
|
9
|
+
stub_request(:put, breakdowns).to_return(status: 200, body: '')
|
8
10
|
|
9
11
|
Airbrake::Config.instance = Airbrake::Config.new(
|
10
12
|
project_id: 1,
|
@@ -73,6 +75,47 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
73
75
|
).to have_been_made
|
74
76
|
end
|
75
77
|
|
78
|
+
it "sends full performance breakdown" do
|
79
|
+
subject.notify(
|
80
|
+
Airbrake::PerformanceBreakdown.new(
|
81
|
+
method: 'DELETE',
|
82
|
+
route: '/routes-breakdowns',
|
83
|
+
response_type: 'json',
|
84
|
+
start_time: Time.new(2018, 1, 1, 0, 49, 0, 0),
|
85
|
+
end_time: Time.new(2018, 1, 1, 0, 50, 0, 0),
|
86
|
+
groups: { db: 131, view: 421 }
|
87
|
+
)
|
88
|
+
)
|
89
|
+
|
90
|
+
expect(
|
91
|
+
a_request(:put, breakdowns).with(body: %r|
|
92
|
+
\A{"routes":\[{
|
93
|
+
"method":"DELETE",
|
94
|
+
"route":"/routes-breakdowns",
|
95
|
+
"responseType":"json",
|
96
|
+
"time":"2018-01-01T00:49:00\+00:00",
|
97
|
+
"count":1,
|
98
|
+
"sum":60000.0,
|
99
|
+
"sumsq":3600000000.0,
|
100
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAUdqYAAB",
|
101
|
+
"groups":{
|
102
|
+
"db":{
|
103
|
+
"count":1,
|
104
|
+
"sum":131.0,
|
105
|
+
"sumsq":17161.0,
|
106
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAUMDAAAB"
|
107
|
+
},
|
108
|
+
"view":{
|
109
|
+
"count":1,
|
110
|
+
"sum":421.0,
|
111
|
+
"sumsq":177241.0,
|
112
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAUPSgAAB"
|
113
|
+
}
|
114
|
+
}
|
115
|
+
}\]}\z|x)
|
116
|
+
).to have_been_made
|
117
|
+
end
|
118
|
+
|
76
119
|
it "rounds time to the floor minute" do
|
77
120
|
subject.notify(
|
78
121
|
Airbrake::Request.new(
|
@@ -177,6 +220,57 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
177
220
|
).to have_been_made
|
178
221
|
end
|
179
222
|
|
223
|
+
it "groups performance breakdowns by route key" do
|
224
|
+
subject.notify(
|
225
|
+
Airbrake::PerformanceBreakdown.new(
|
226
|
+
method: 'DELETE',
|
227
|
+
route: '/routes-breakdowns',
|
228
|
+
response_type: 'json',
|
229
|
+
start_time: Time.new(2018, 1, 1, 0, 0, 20, 0),
|
230
|
+
end_time: Time.new(2018, 1, 1, 0, 0, 22, 0),
|
231
|
+
groups: { db: 131, view: 421 }
|
232
|
+
)
|
233
|
+
)
|
234
|
+
subject.notify(
|
235
|
+
Airbrake::PerformanceBreakdown.new(
|
236
|
+
method: 'DELETE',
|
237
|
+
route: '/routes-breakdowns',
|
238
|
+
response_type: 'json',
|
239
|
+
start_time: Time.new(2018, 1, 1, 0, 0, 30, 0),
|
240
|
+
end_time: Time.new(2018, 1, 1, 0, 0, 32, 0),
|
241
|
+
groups: { db: 55, view: 11 }
|
242
|
+
)
|
243
|
+
)
|
244
|
+
|
245
|
+
expect(
|
246
|
+
a_request(:put, breakdowns).with(body: %r|
|
247
|
+
\A{"routes":\[{
|
248
|
+
"method":"DELETE",
|
249
|
+
"route":"/routes-breakdowns",
|
250
|
+
"responseType":"json",
|
251
|
+
"time":"2018-01-01T00:00:00\+00:00",
|
252
|
+
"count":2,
|
253
|
+
"sum":4000.0,
|
254
|
+
"sumsq":8000000.0,
|
255
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAUT6AAAC",
|
256
|
+
"groups":{
|
257
|
+
"db":{
|
258
|
+
"count":2,
|
259
|
+
"sum":186.0,
|
260
|
+
"sumsq":20186.0,
|
261
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAkJcAABCmAAAAQE="
|
262
|
+
},
|
263
|
+
"view":{
|
264
|
+
"count":2,
|
265
|
+
"sum":432.0,
|
266
|
+
"sumsq":177362.0,
|
267
|
+
"tdigest":"AAAAAkA0AAAAAAAAAAAAAkEwAABDzQAAAQE="
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}\]}\z|x)
|
271
|
+
).to have_been_made
|
272
|
+
end
|
273
|
+
|
180
274
|
it "returns a promise" do
|
181
275
|
promise = subject.notify(
|
182
276
|
Airbrake::Request.new(
|
@@ -339,9 +433,7 @@ RSpec.describe Airbrake::PerformanceNotifier do
|
|
339
433
|
describe "#delete_filter" do
|
340
434
|
let(:filter) do
|
341
435
|
Class.new do
|
342
|
-
def call(resource)
|
343
|
-
resource.ignore!
|
344
|
-
end
|
436
|
+
def call(resource); end
|
345
437
|
end
|
346
438
|
end
|
347
439
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/stat_spec.rb
CHANGED
@@ -12,21 +12,19 @@ RSpec.describe Airbrake::Stat do
|
|
12
12
|
|
13
13
|
describe "#increment" do
|
14
14
|
let(:start_time) { Time.new(2018, 1, 1, 0, 0, 20, 0) }
|
15
|
-
let(:end_time) { Time.new(2018, 1, 1, 0, 0,
|
15
|
+
let(:end_time) { Time.new(2018, 1, 1, 0, 0, 22, 0) }
|
16
16
|
|
17
17
|
before { subject.increment(start_time, end_time) }
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
19
|
+
its(:sum) { is_expected.to eq(2000) }
|
20
|
+
end
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
end
|
22
|
+
describe "#increment_ms" do
|
23
|
+
before { subject.increment_ms(1000) }
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
its(:count) { is_expected.to eq(1) }
|
26
|
+
its(:sum) { is_expected.to eq(1000) }
|
27
|
+
its(:sumsq) { is_expected.to eq(1000000) }
|
30
28
|
|
31
29
|
it "updates tdigest" do
|
32
30
|
expect(subject.tdigest.size).to eq(1)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Airbrake Technologies, Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rbtree3
|
@@ -68,6 +68,7 @@ files:
|
|
68
68
|
- lib/airbrake-ruby/nested_exception.rb
|
69
69
|
- lib/airbrake-ruby/notice.rb
|
70
70
|
- lib/airbrake-ruby/notice_notifier.rb
|
71
|
+
- lib/airbrake-ruby/performance_breakdown.rb
|
71
72
|
- lib/airbrake-ruby/performance_notifier.rb
|
72
73
|
- lib/airbrake-ruby/promise.rb
|
73
74
|
- lib/airbrake-ruby/query.rb
|