Skip to content

Commit ef2ea49

Browse files
authoredFeb 14, 2025··
Prevent starting Vernier in nested transactions (#2528)
* Prevent starting Vernier in nested transactions * Update CHANGELOG * Document new Hub methods as private
1 parent ca7d386 commit ef2ea49

File tree

6 files changed

+80
-6
lines changed

6 files changed

+80
-6
lines changed
 

‎CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
### Bug Fixes
88

99
- Fix argument serialization for ranges that consist of ActiveSupport::TimeWithZone ([#2548](https://github.com/getsentry/sentry-ruby/pull/2548))
10+
- Prevent starting Vernier in nested transactions ([#2528](https://github.com/getsentry/sentry-ruby/pull/2528))
1011

1112
### Miscellaneous
1213

‎sentry-ruby/lib/sentry/hub.rb

+31-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,42 @@ module Sentry
88
class Hub
99
include ArgumentCheckingHelper
1010

11+
MUTEX = Mutex.new
12+
1113
attr_reader :last_event_id
1214

15+
attr_reader :current_profiler
16+
1317
def initialize(client, scope)
1418
first_layer = Layer.new(client, scope)
1519
@stack = [first_layer]
1620
@last_event_id = nil
21+
@current_profiler = {}
22+
end
23+
24+
# This is an internal private method
25+
# @api private
26+
def start_profiler!(transaction)
27+
MUTEX.synchronize do
28+
transaction.start_profiler!
29+
@current_profiler[transaction.__id__] = transaction.profiler
30+
end
31+
end
32+
33+
# This is an internal private method
34+
# @api private
35+
def stop_profiler!(transaction)
36+
MUTEX.synchronize do
37+
@current_profiler.delete(transaction.__id__)&.stop
38+
end
39+
end
40+
41+
# This is an internal private method
42+
# @api private
43+
def profiler_running?
44+
MUTEX.synchronize do
45+
!@current_profiler.empty?
46+
end
1747
end
1848

1949
def new_from_top
@@ -96,7 +126,7 @@ def start_transaction(transaction: nil, custom_sampling_context: {}, instrumente
96126
sampling_context.merge!(custom_sampling_context)
97127
transaction.set_initial_sample_decision(sampling_context: sampling_context)
98128

99-
transaction.start_profiler!
129+
start_profiler!(transaction)
100130

101131
transaction
102132
end

‎sentry-ruby/lib/sentry/transaction.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,11 @@ def initialize(
8585
@effective_sample_rate = nil
8686
@contexts = {}
8787
@measurements = {}
88-
@profiler = @configuration.profiler_class.new(@configuration)
88+
89+
unless @hub.profiler_running?
90+
@profiler = @configuration.profiler_class.new(@configuration)
91+
end
92+
8993
init_span_recorder
9094
end
9195

@@ -257,7 +261,7 @@ def finish(hub: nil, end_timestamp: nil)
257261
@name = UNLABELD_NAME
258262
end
259263

260-
@profiler.stop
264+
@hub.stop_profiler!(self)
261265

262266
if @sampled
263267
event = hub.current_client.event_from_transaction(self)
@@ -299,6 +303,8 @@ def set_context(key, value)
299303
# Start the profiler.
300304
# @return [void]
301305
def start_profiler!
306+
return unless profiler
307+
302308
profiler.set_initial_sample_decision(sampled)
303309
profiler.start
304310
end

‎sentry-ruby/lib/sentry/transaction_event.rb

+4-1
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ def to_hash
5959

6060
private
6161

62+
EMPTY_PROFILE = {}.freeze
63+
6264
def populate_profile(transaction)
63-
profile_hash = transaction.profiler.to_hash
65+
profile_hash = transaction.profiler&.to_hash || EMPTY_PROFILE
66+
6467
return if profile_hash.empty?
6568

6669
profile_hash.merge!(

‎sentry-ruby/lib/sentry/vernier/profiler.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def stop
7474
return unless @started
7575

7676
@result = ::Vernier.stop_profile
77+
@started = false
7778

7879
log("Stopped")
7980
rescue RuntimeError => e
@@ -89,13 +90,13 @@ def active_thread_id
8990
end
9091

9192
def to_hash
92-
return EMPTY_RESULT unless @started
93-
9493
unless @sampled
9594
record_lost_event(:sample_rate)
9695
return EMPTY_RESULT
9796
end
9897

98+
return EMPTY_RESULT unless result
99+
99100
{ **profile_meta, profile: output.to_h }
100101
end
101102

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
require "spec_helper"
4+
require 'contexts/with_request_mock'
5+
6+
RSpec.describe Sentry, 'transactions / profiler', when: [:vernier_installed?, :rack_available?] do
7+
include_context "with request mock"
8+
9+
before do
10+
perform_basic_setup do |config|
11+
config.traces_sample_rate = 1.0
12+
config.profiles_sample_rate = 1.0
13+
config.profiler_class = Sentry::Vernier::Profiler
14+
end
15+
end
16+
17+
it 'starts profiler just once inside nested transactions' do
18+
10.times do
19+
parent_transaction = Sentry.start_transaction(name: "parent")
20+
nested_transaction = Sentry.start_transaction(name: "nested")
21+
22+
ProfilerTest::Bar.bar
23+
24+
expect(Sentry.get_current_hub.profiler_running?).to be(true)
25+
26+
expect(parent_transaction.profiler).to_not be_nil
27+
expect(nested_transaction.profiler).to be_nil
28+
29+
nested_transaction.finish
30+
parent_transaction.finish
31+
end
32+
end
33+
end

0 commit comments

Comments
 (0)
Please sign in to comment.