airbrake-ruby 4.1.0-java → 4.2.0-java

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74ea2824b27fc1ba6045ba69e9e1fd0db21cd4c5
4
- data.tar.gz: fca6e05b839805cb5d4b4171145c1dfeb4b4890b
3
+ metadata.gz: 15e4e6fd4395f0687d8acfe52f59e71090d8be9a
4
+ data.tar.gz: 3db1f8a4dc6f496c1067d7d0e295054ee6c8fce2
5
5
  SHA512:
6
- metadata.gz: acd47ef0a467be397e938e67b8d6b6cfcb0137840ae7cff20c2d4a7e46add72364530af14062990b47a017b44ddb5325953a39400fc1982968c6dc144456e46a
7
- data.tar.gz: d96f9bb73da48029a993bda925ec727a945177300721eb521f5966bac962f6d9ae2941cd191b4bc2d5497c07da7764c5d7c740bf5bb7fc0a30b61c50b263c08f
6
+ metadata.gz: c42a855a144bd9d07d57d9fdb52f4d961c46269d0ad1e6ffdaed614e40c8ce4bb1856438087e54e844f747f46dcf4217be7b49035605d2aa39ab8fc23dcb9a36
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
- @payload[resource] ||= Airbrake::Stat.new
38
- @payload[resource].increment(resource.start_time, resource.end_time)
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.group_by { |k, _v| k.name }.each do |resource_name, data|
85
- data = { resource_name => data.map { |k, v| k.to_h.merge!(v.to_h) } }
86
- data['environment'] = @config.environment if @config.environment
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
@@ -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 name
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 name
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
@@ -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
 
@@ -2,5 +2,5 @@
2
2
  # More information: http://semver.org/
3
3
  module Airbrake
4
4
  # @return [String] the library version
5
- AIRBRAKE_RUBY_VERSION = '4.1.0'.freeze
5
+ AIRBRAKE_RUBY_VERSION = '4.2.0'.freeze
6
6
  end
@@ -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
@@ -1,3 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV['COVERAGE']
3
+
1
4
  require 'airbrake-ruby'
2
5
 
3
6
  require 'rspec/its'
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, 21, 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
- it "increments count" do
20
- expect(subject.count).to eq(1)
21
- end
19
+ its(:sum) { is_expected.to eq(2000) }
20
+ end
22
21
 
23
- it "updates sum" do
24
- expect(subject.sum).to eq(1000)
25
- end
22
+ describe "#increment_ms" do
23
+ before { subject.increment_ms(1000) }
26
24
 
27
- it "updates sumsq" do
28
- expect(subject.sumsq).to eq(1000000)
29
- end
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.1.0
4
+ version: 4.2.0
5
5
  platform: java
6
6
  authors:
7
7
  - Airbrake Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-02-28 00:00:00.000000000 Z
11
+ date: 2019-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbtree-jruby
@@ -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