Skip to content

Commit

Permalink
Add Sentry::Metrics.timing API to measure blocks (#2254)
Browse files Browse the repository at this point in the history
  • Loading branch information
sl0thentr0py committed Mar 12, 2024
1 parent d13923b commit cf8f7ae
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 0 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Add `hint:` support to `Sentry::Rails::ErrorSubscriber` [#2235](https://github.com/getsentry/sentry-ruby/pull/2235)
- Add [Metrics](https://docs.sentry.io/product/metrics/) support
- Add main APIs and `Aggregator` thread [#2247](https://github.com/getsentry/sentry-ruby/pull/2247)
- Add `Sentry::Metrics.timing` API for measuring block duration [#2254](https://github.com/getsentry/sentry-ruby/pull/2254)

The SDK now supports recording and aggregating metrics. A new thread will be started
for aggregation and will flush the pending data to Sentry every 5 seconds.
Expand Down Expand Up @@ -36,6 +37,11 @@

# set - get unique counts of elements
Sentry::Metrics.set('user_view', 'jane')

# timing - measure duration of code block, defaults to seconds
Sentry::Metrics.timing('how_long') { sleep(1) }
# timing - measure duration of code block in other duraton units
Sentry::Metrics.timing('how_long_ms', unit: 'millisecond') { sleep(0.5) }
```

### Bug Fixes
Expand Down
17 changes: 17 additions & 0 deletions sentry-ruby/lib/sentry/metrics.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
require 'sentry/metrics/distribution_metric'
require 'sentry/metrics/gauge_metric'
require 'sentry/metrics/set_metric'
require 'sentry/metrics/timing'
require 'sentry/metrics/aggregator'

module Sentry
module Metrics
DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
FRACTIONAL_UNITS = %w[ratio percent]

class << self
def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
Expand All @@ -25,6 +30,18 @@ def set(key, value, unit: 'none', tags: {}, timestamp: nil)
def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
end

def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
return unless Sentry.metrics_aggregator
return unless block_given?
return unless DURATION_UNITS.include?(unit)

start = Timing.send(unit.to_sym)
yield
value = Timing.send(unit.to_sym) - start

Sentry.metrics_aggregator.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
end
end
end
end
43 changes: 43 additions & 0 deletions sentry-ruby/lib/sentry/metrics/timing.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

module Sentry
module Metrics
module Timing
class << self
def nanosecond
time = Sentry.utc_now
time.to_i * (10 ** 9) + time.nsec
end

def microsecond
time = Sentry.utc_now
time.to_i * (10 ** 6) + time.usec
end

def millisecond
Sentry.utc_now.to_i * (10 ** 3)
end

def second
Sentry.utc_now.to_i
end

def minute
Sentry.utc_now.to_i / 60.0
end

def hour
Sentry.utc_now.to_i / 3600.0
end

def day
Sentry.utc_now.to_i / (3600.0 * 24.0)
end

def week
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
end
end
end
end
end
54 changes: 54 additions & 0 deletions sentry-ruby/spec/sentry/metrics/timing_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
require 'spec_helper'

RSpec.describe Sentry::Metrics::Timing do
let(:fake_time) { Time.new(2024, 1, 2, 3, 4, 5) }
before { allow(Time).to receive(:now).and_return(fake_time) }

describe '.nanosecond' do
it 'returns nanoseconds' do
expect(described_class.nanosecond).to eq(fake_time.to_i * 10 ** 9)
end
end

describe '.microsecond' do
it 'returns microseconds' do
expect(described_class.microsecond).to eq(fake_time.to_i * 10 ** 6)
end
end

describe '.millisecond' do
it 'returns milliseconds' do
expect(described_class.millisecond).to eq(fake_time.to_i * 10 ** 3)
end
end

describe '.second' do
it 'returns seconds' do
expect(described_class.second).to eq(fake_time.to_i)
end
end

describe '.minute' do
it 'returns minutes' do
expect(described_class.minute).to eq(fake_time.to_i / 60.0)
end
end

describe '.hour' do
it 'returns hours' do
expect(described_class.hour).to eq(fake_time.to_i / 3600.0)
end
end

describe '.day' do
it 'returns days' do
expect(described_class.day).to eq(fake_time.to_i / (3600.0 * 24.0))
end
end

describe '.week' do
it 'returns weeks' do
expect(described_class.week).to eq(fake_time.to_i / (3600.0 * 24.0 * 7.0))
end
end
end
25 changes: 25 additions & 0 deletions sentry-ruby/spec/sentry/metrics_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,29 @@
described_class.gauge('foo', 5.0, unit: 'second', tags: { fortytwo: 42 }, timestamp: fake_time)
end
end

describe '.timing' do
it 'does nothing without a block' do
expect(aggregator).not_to receive(:add)
described_class.timing('foo')
end

it 'does nothing with a non-duration unit' do
expect(aggregator).not_to receive(:add)
described_class.timing('foo', unit: 'ratio') { }
end

it 'measures time taken as distribution and passes through args to aggregator' do
expect(aggregator).to receive(:add).with(
:d,
'foo',
an_instance_of(Integer),
unit: 'millisecond',
tags: { fortytwo: 42 },
timestamp: fake_time
)

described_class.timing('foo', unit: 'millisecond', tags: { fortytwo: 42 }, timestamp: fake_time) { sleep(0.1) }
end
end
end

0 comments on commit cf8f7ae

Please sign in to comment.