Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sentry::Metrics.timing API to measure blocks #2254

Merged
merged 19 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading