Skip to content

Commit 175cde3

Browse files
committed
Add Sentry::Metrics.timing API to measure blocks
1 parent 8f9dabb commit 175cde3

File tree

5 files changed

+145
-0
lines changed

5 files changed

+145
-0
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Add `hint:` support to `Sentry::Rails::ErrorSubscriber` [#2235](https://github.com/getsentry/sentry-ruby/pull/2235)
88
- Add [Metrics](https://docs.sentry.io/product/metrics/) support
99
- Add main APIs and `Aggregator` thread [#2247](https://github.com/getsentry/sentry-ruby/pull/2247)
10+
- Add `Sentry::Metrics.timing` API for measuring block duration [#2254](https://github.com/getsentry/sentry-ruby/pull/2254)
1011

1112
The SDK now supports recording and aggregating metrics. A new thread will be started
1213
for aggregation and will flush the pending data to Sentry every 5 seconds.
@@ -36,6 +37,11 @@
3637
3738
# set - get unique counts of elements
3839
Sentry::Metrics.set('user_view', 'jane')
40+
41+
# timing - measure duration of code block, defaults to seconds
42+
Sentry::Metrics.timing('how_long') { sleep(1) }
43+
# timing - measure duration of code block in other duraton units
44+
Sentry::Metrics.timing('how_long_ms', unit: 'millisecond') { sleep(0.5) }
3945
```
4046

4147
### Bug Fixes

sentry-ruby/lib/sentry/metrics.rb

+17
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
require 'sentry/metrics/distribution_metric'
66
require 'sentry/metrics/gauge_metric'
77
require 'sentry/metrics/set_metric'
8+
require 'sentry/metrics/timing'
89
require 'sentry/metrics/aggregator'
910

1011
module Sentry
1112
module Metrics
13+
DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
14+
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
15+
FRACTIONAL_UNITS = %w[ratio percent]
16+
1217
class << self
1318
def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
1419
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
@@ -25,6 +30,18 @@ def set(key, value, unit: 'none', tags: {}, timestamp: nil)
2530
def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
2631
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
2732
end
33+
34+
def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
35+
return unless Sentry.metrics_aggregator
36+
return unless block_given?
37+
return unless DURATION_UNITS.include?(unit)
38+
39+
start = Timing.send(unit.to_sym)
40+
yield
41+
value = Timing.send(unit.to_sym) - start
42+
43+
Sentry.metrics_aggregator.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
44+
end
2845
end
2946
end
3047
end
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
module Sentry
4+
module Metrics
5+
module Timing
6+
class << self
7+
def nanosecond
8+
time = Sentry.utc_now
9+
time.to_i * (10 ** 9) + time.nsec
10+
end
11+
12+
def microsecond
13+
time = Sentry.utc_now
14+
time.to_i * (10 ** 6) + time.usec
15+
end
16+
17+
def millisecond
18+
Sentry.utc_now.to_i * (10 ** 3)
19+
end
20+
21+
def second
22+
Sentry.utc_now.to_i
23+
end
24+
25+
def minute
26+
Sentry.utc_now.to_i / 60.0
27+
end
28+
29+
def hour
30+
Sentry.utc_now.to_i / 3600.0
31+
end
32+
33+
def day
34+
Sentry.utc_now.to_i / (3600.0 * 24.0)
35+
end
36+
37+
def week
38+
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
39+
end
40+
end
41+
end
42+
end
43+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe Sentry::Metrics::Timing do
4+
let(:fake_time) { Time.new(2024, 1, 2, 3, 4, 5) }
5+
before { allow(Time).to receive(:now).and_return(fake_time) }
6+
7+
describe '.nanosecond' do
8+
it 'returns nanoseconds' do
9+
expect(described_class.nanosecond).to eq(fake_time.to_i * 10 ** 9)
10+
end
11+
end
12+
13+
describe '.microsecond' do
14+
it 'returns microseconds' do
15+
expect(described_class.microsecond).to eq(fake_time.to_i * 10 ** 6)
16+
end
17+
end
18+
19+
describe '.millisecond' do
20+
it 'returns milliseconds' do
21+
expect(described_class.millisecond).to eq(fake_time.to_i * 10 ** 3)
22+
end
23+
end
24+
25+
describe '.second' do
26+
it 'returns seconds' do
27+
expect(described_class.second).to eq(fake_time.to_i)
28+
end
29+
end
30+
31+
describe '.minute' do
32+
it 'returns minutes' do
33+
expect(described_class.minute).to eq(fake_time.to_i / 60.0)
34+
end
35+
end
36+
37+
describe '.hour' do
38+
it 'returns hours' do
39+
expect(described_class.hour).to eq(fake_time.to_i / 3600.0)
40+
end
41+
end
42+
43+
describe '.day' do
44+
it 'returns days' do
45+
expect(described_class.day).to eq(fake_time.to_i / (3600.0 * 24.0))
46+
end
47+
end
48+
49+
describe '.week' do
50+
it 'returns weeks' do
51+
expect(described_class.week).to eq(fake_time.to_i / (3600.0 * 24.0 * 7.0))
52+
end
53+
end
54+
end

sentry-ruby/spec/sentry/metrics_spec.rb

+25
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,29 @@
8282
described_class.gauge('foo', 5.0, unit: 'second', tags: { fortytwo: 42 }, timestamp: fake_time)
8383
end
8484
end
85+
86+
describe '.timing' do
87+
it 'does nothing without a block' do
88+
expect(aggregator).not_to receive(:add)
89+
described_class.timing('foo')
90+
end
91+
92+
it 'does nothing with a non-duration unit' do
93+
expect(aggregator).not_to receive(:add)
94+
described_class.timing('foo', unit: 'ratio') { }
95+
end
96+
97+
it 'measures time taken as distribution and passes through args to aggregator' do
98+
expect(aggregator).to receive(:add).with(
99+
:d,
100+
'foo',
101+
an_instance_of(Integer),
102+
unit: 'millisecond',
103+
tags: { fortytwo: 42 },
104+
timestamp: fake_time
105+
)
106+
107+
described_class.timing('foo', unit: 'millisecond', tags: { fortytwo: 42 }, timestamp: fake_time) { sleep(0.1) }
108+
end
109+
end
85110
end

0 commit comments

Comments
 (0)