Skip to content

Commit 763c8ad

Browse files
committed
Turn SampleRand into a class
1 parent 0fdbceb commit 763c8ad

File tree

11 files changed

+245
-105
lines changed

11 files changed

+245
-105
lines changed

sentry-ruby/lib/sentry/propagation_context.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -146,22 +146,24 @@ def extract_sample_rand_from_baggage(baggage)
146146
sample_rand_str = baggage.items["sample_rand"]
147147
return unless sample_rand_str
148148

149-
sample_rand = sample_rand_str.to_f
150-
Utils::SampleRand.valid?(sample_rand) ? sample_rand : nil
149+
generator = Utils::SampleRand.new(trace_id: @trace_id)
150+
generator.generate_from_value(sample_rand_str)
151151
end
152152

153153
def generate_sample_rand
154+
generator = Utils::SampleRand.new(trace_id: @trace_id)
155+
154156
if @incoming_trace && !@parent_sampled.nil? && @baggage
155157
sample_rate_str = @baggage.items["sample_rate"]
156158
sample_rate = sample_rate_str&.to_f
157159

158160
if sample_rate && !@parent_sampled.nil?
159-
Utils::SampleRand.generate_from_sampling_decision(@parent_sampled, sample_rate, @trace_id)
161+
generator.generate_from_sampling_decision(@parent_sampled, sample_rate)
160162
else
161-
Utils::SampleRand.generate_from_trace_id(@trace_id)
163+
generator.generate_from_trace_id
162164
end
163165
else
164-
Utils::SampleRand.generate_from_trace_id(@trace_id)
166+
generator.generate_from_trace_id
165167
end
166168
end
167169
end

sentry-ruby/lib/sentry/transaction.rb

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,10 @@ def initialize(
9696

9797
init_span_recorder
9898

99-
@sample_rand ||= Utils::SampleRand.generate_from_trace_id(@trace_id)
99+
unless @sample_rand
100+
generator = Utils::SampleRand.new(trace_id: @trace_id)
101+
@sample_rand = generator.generate_from_trace_id
102+
end
100103
end
101104

102105
# @deprecated use Sentry.continue_trace instead.
@@ -152,20 +155,25 @@ def self.extract_sentry_trace(sentry_trace)
152155
end
153156

154157
def self.extract_sample_rand_from_baggage(baggage, trace_id, parent_sampled)
155-
return Utils::SampleRand.generate_from_trace_id(trace_id) unless baggage&.items
158+
generator = Utils::SampleRand.new(trace_id: trace_id)
159+
160+
unless baggage&.items
161+
return generator.generate_from_trace_id
162+
end
156163

157164
sample_rand_str = baggage.items["sample_rand"]
158-
if sample_rand_str && Utils::SampleRand.valid?(sample_rand_str.to_f)
159-
sample_rand_str.to_f
160-
else
161-
sample_rate_str = baggage.items["sample_rate"]
162-
sample_rate = sample_rate_str&.to_f
163165

164-
if sample_rate && parent_sampled != nil
165-
Utils::SampleRand.generate_from_sampling_decision(parent_sampled, sample_rate, trace_id)
166-
else
167-
Utils::SampleRand.generate_from_trace_id(trace_id)
168-
end
166+
if sample_rand_str
167+
return generator.generate_from_value(sample_rand_str)
168+
end
169+
170+
sample_rate_str = baggage.items["sample_rate"]
171+
sample_rate = sample_rate_str&.to_f
172+
173+
if sample_rate && parent_sampled != nil
174+
generator.generate_from_sampling_decision(parent_sampled, sample_rate)
175+
else
176+
generator.generate_from_trace_id
169177
end
170178
end
171179

sentry-ruby/lib/sentry/utils/sample_rand.rb

Lines changed: 76 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,96 @@
22

33
module Sentry
44
module Utils
5-
module SampleRand
6-
def self.generate_from_trace_id(trace_id)
7-
(random_from_trace_id(trace_id) * 1_000_000).floor / 1_000_000.0
5+
class SampleRand
6+
PRECISION = 1_000_000.0
7+
FORMAT_PRECISION = 6
8+
9+
attr_reader :trace_id
10+
11+
def self.valid?(value)
12+
return false unless value
13+
value >= 0.0 && value < 1.0
14+
end
15+
16+
def self.format(value)
17+
return unless value
18+
19+
truncated = (value * PRECISION).floor / PRECISION
20+
"%.#{FORMAT_PRECISION}f" % truncated
21+
end
22+
23+
def initialize(trace_id: nil)
24+
@trace_id = trace_id
825
end
926

10-
def self.generate_from_sampling_decision(sampled, sample_rate, trace_id = nil)
11-
if sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
12-
trace_id ? generate_from_trace_id(trace_id) : format(Random.rand(1.0)).to_f
27+
def generate_from_trace_id
28+
(random_from_trace_id * PRECISION).floor / PRECISION
29+
end
30+
31+
def generate_from_sampling_decision(sampled, sample_rate)
32+
if invalid_sample_rate?(sample_rate)
33+
fallback_generation
1334
else
14-
random = random_from_trace_id(trace_id)
15-
16-
if sampled
17-
format(random * sample_rate)
18-
elsif sample_rate == 1.0
19-
format(random)
20-
else
21-
format(sample_rate + random * (1.0 - sample_rate))
22-
end.to_f
35+
generate_based_on_sampling(sampled, sample_rate)
2336
end
2437
end
2538

26-
def self.random_from_trace_id(trace_id)
27-
if trace_id
28-
Random.new(trace_id[0, 16].to_i(16))
39+
def generate_from_value(sample_rand_value)
40+
parsed_value = parse_value(sample_rand_value)
41+
42+
if self.class.valid?(parsed_value)
43+
parsed_value
44+
else
45+
fallback_generation
46+
end
47+
end
48+
49+
private
50+
51+
def random_from_trace_id
52+
if @trace_id
53+
Random.new(@trace_id[0, 16].to_i(16))
2954
else
3055
Random.new
3156
end.rand(1.0)
3257
end
3358

34-
def self.valid?(sample_rand)
35-
return false unless sample_rand
36-
return false if sample_rand.is_a?(String) && sample_rand.empty?
59+
def invalid_sample_rate?(sample_rate)
60+
sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
61+
end
3762

38-
value = sample_rand.is_a?(String) ? sample_rand.to_f : sample_rand
39-
value >= 0.0 && value < 1.0
63+
def fallback_generation
64+
if @trace_id
65+
(random_from_trace_id * PRECISION).floor / PRECISION
66+
else
67+
format_random(Random.rand(1.0))
68+
end
69+
end
70+
71+
def generate_based_on_sampling(sampled, sample_rate)
72+
random = random_from_trace_id
73+
74+
result = if sampled
75+
random * sample_rate
76+
elsif sample_rate == 1.0
77+
random
78+
else
79+
sample_rate + random * (1.0 - sample_rate)
80+
end
81+
82+
format_random(result)
4083
end
4184

42-
def self.format(sample_rand)
43-
truncated = (sample_rand * 1_000_000).floor / 1_000_000.0
44-
"%.6f" % truncated
85+
def format_random(value)
86+
truncated = (value * PRECISION).floor / PRECISION
87+
("%.#{FORMAT_PRECISION}f" % truncated).to_f
88+
end
89+
90+
def parse_value(sample_rand_value)
91+
return unless sample_rand_value
92+
return if sample_rand_value.is_a?(String) && sample_rand_value.empty?
93+
94+
sample_rand_value.is_a?(String) ? sample_rand_value.to_f : sample_rand_value
4595
end
4696
end
4797
end

sentry-ruby/spec/sentry/hub_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,8 @@
715715

716716
expect(transaction.sample_rand).to eq(propagation_context.sample_rand)
717717

718-
expected = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
718+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
719+
expected = generator.generate_from_trace_id
719720

720721
expect(transaction.sample_rand).to eq(expected)
721722
end

sentry-ruby/spec/sentry/propagation_context/sample_rand_spec.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,11 @@
103103
it "uses parent's explicit unsampled decision instead of falling back to trace_id generation" do
104104
context = described_class.new(scope, env)
105105

106-
expected_from_decision = Sentry::Utils::SampleRand.generate_from_sampling_decision(false, 0.5, "771a43a4192642f0b136d5159a501700")
107-
expected_from_trace_id = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
106+
generator1 = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
107+
expected_from_decision = generator1.generate_from_sampling_decision(false, 0.5)
108+
109+
generator2 = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
110+
expected_from_trace_id = generator2.generate_from_trace_id
108111

109112
expect(context.sample_rand).to eq(expected_from_decision)
110113
expect(context.sample_rand).not_to eq(expected_from_trace_id)
@@ -126,7 +129,8 @@
126129
expect(context.sample_rand).to be < 1.0
127130
expect(context.incoming_trace).to be true
128131

129-
expected = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
132+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
133+
expected = generator.generate_from_trace_id
130134
expect(context.sample_rand).to eq(expected)
131135
end
132136
end

sentry-ruby/spec/sentry/rack/capture_exceptions_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,11 @@ def verify_transaction_doesnt_inherit_external_transaction(transaction, external
279279
end
280280

281281
def wont_be_sampled_by_sdk
282-
allow(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(1.0)
282+
allow_any_instance_of(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(1.0)
283283
end
284284

285285
def will_be_sampled_by_sdk
286-
allow(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.3)
286+
allow_any_instance_of(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.3)
287287
end
288288

289289
before do
@@ -430,7 +430,7 @@ def will_be_sampled_by_sdk
430430

431431
context "when the transaction is sampled" do
432432
before do
433-
allow(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.4)
433+
allow_any_instance_of(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.4)
434434
end
435435

436436
it "starts a transaction and finishes it" do
@@ -488,7 +488,7 @@ def will_be_sampled_by_sdk
488488

489489
context "when the transaction is not sampled" do
490490
before do
491-
allow(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(1.0)
491+
allow_any_instance_of(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(1.0)
492492
end
493493

494494
it "doesn't do anything" do
@@ -506,7 +506,7 @@ def will_be_sampled_by_sdk
506506

507507
context "when there's an exception" do
508508
before do
509-
allow(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.4)
509+
allow_any_instance_of(Sentry::Utils::SampleRand).to receive(:generate_from_trace_id).and_return(0.4)
510510
end
511511

512512
it "still finishes the transaction" do

sentry-ruby/spec/sentry/transaction_spec.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,46 @@
658658
end
659659
end
660660

661+
describe ".extract_sample_rand_from_baggage" do
662+
let(:trace_id) { "771a43a4192642f0b136d5159a501700" }
663+
664+
it "returns trace_id generation when baggage is nil" do
665+
result = described_class.extract_sample_rand_from_baggage(nil, trace_id, true)
666+
667+
generator = Sentry::Utils::SampleRand.new(trace_id: trace_id)
668+
expected = generator.generate_from_trace_id
669+
670+
expect(result).to eq(expected)
671+
end
672+
673+
it "returns trace_id generation when baggage has no items" do
674+
baggage = double("baggage", items: nil)
675+
result = described_class.extract_sample_rand_from_baggage(baggage, trace_id, true)
676+
677+
generator = Sentry::Utils::SampleRand.new(trace_id: trace_id)
678+
expected = generator.generate_from_trace_id
679+
680+
expect(result).to eq(expected)
681+
end
682+
683+
it "returns trace_id generation when sample_rand is invalid" do
684+
baggage = double("baggage", items: { "sample_rand" => "1.5" })
685+
result = described_class.extract_sample_rand_from_baggage(baggage, trace_id, true)
686+
687+
generator = Sentry::Utils::SampleRand.new(trace_id: trace_id)
688+
expected = generator.generate_from_trace_id
689+
690+
expect(result).to eq(expected)
691+
end
692+
693+
it "returns valid sample_rand from baggage when present" do
694+
baggage = double("baggage", items: { "sample_rand" => "0.5" })
695+
result = described_class.extract_sample_rand_from_baggage(baggage, trace_id, true)
696+
697+
expect(result).to eq(0.5)
698+
end
699+
end
700+
661701
describe "#set_name" do
662702
it "sets name and source directly" do
663703
subject.set_name("bar", source: :url)

sentry-ruby/spec/sentry/transactions/sample_rand_propagation_spec.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434

3535
expect(transaction.sample_rand).to eq(propagation_context.sample_rand)
3636

37-
expected = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
37+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
38+
expected = generator.generate_from_trace_id
3839

3940
expect(transaction.sample_rand).to eq(expected)
4041
expect(propagation_context.sample_rand).to eq(expected)
@@ -90,7 +91,8 @@
9091
transaction = Sentry.continue_trace(env, name: "test")
9192
propagation_context = Sentry.get_current_scope.propagation_context
9293

93-
expected = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
94+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
95+
expected = generator.generate_from_trace_id
9496

9597
expect(transaction.sample_rand).to eq(expected)
9698
expect(propagation_context.sample_rand).to eq(expected)

sentry-ruby/spec/sentry/transactions/trace_propagation_spec.rb

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,19 +68,15 @@
6868

6969
expect(transaction).not_to be_nil
7070

71-
expected_sample_rand = Sentry::Utils::SampleRand.generate_from_sampling_decision(
72-
true,
73-
0.25,
74-
"771a43a4192642f0b136d5159a501700"
75-
)
71+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
72+
expected_sample_rand = generator.generate_from_sampling_decision(true, 0.25)
7673

7774
expect(expected_sample_rand).to be >= 0.0
7875
expect(expected_sample_rand).to be < 1.0
7976
expect(expected_sample_rand).to be < 0.25
8077

81-
expected_sample_rand2 = Sentry::Utils::SampleRand.generate_from_sampling_decision(
82-
true, 0.25, "771a43a4192642f0b136d5159a501700"
83-
)
78+
generator2 = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
79+
expected_sample_rand2 = generator2.generate_from_sampling_decision(true, 0.25)
8480
expect(expected_sample_rand2).to eq(expected_sample_rand)
8581

8682
baggage = transaction.get_baggage
@@ -125,9 +121,9 @@
125121

126122
expect(sample_rands.uniq.length).to eq(1)
127123

128-
expected_sample_rand = Sentry::Utils::SampleRand.format(
129-
Sentry::Utils::SampleRand.generate_from_trace_id(trace_id)
130-
)
124+
generator = Sentry::Utils::SampleRand.new(trace_id: trace_id)
125+
value = generator.generate_from_trace_id
126+
expected_sample_rand = Sentry::Utils::SampleRand.format(value)
131127
expect(sample_rands.first).to eq(expected_sample_rand)
132128
end
133129

@@ -165,7 +161,8 @@
165161

166162
expect(transaction).not_to be_nil
167163

168-
expected_sample_rand = Sentry::Utils::SampleRand.generate_from_trace_id("771a43a4192642f0b136d5159a501700")
164+
generator = Sentry::Utils::SampleRand.new(trace_id: "771a43a4192642f0b136d5159a501700")
165+
expected_sample_rand = generator.generate_from_trace_id
169166

170167
expect(expected_sample_rand).to be >= 0.0
171168
expect(expected_sample_rand).to be < 1.0

0 commit comments

Comments
 (0)