Skip to content

Commit 8570a4e

Browse files
committed
update test case
1 parent d26c5d6 commit 8570a4e

File tree

12 files changed

+1587
-28
lines changed

12 files changed

+1587
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ rubocop_result.txt
7474
# sample script
7575
git_push.sh
7676
test_run.sh
77+
test.js
7778

7879
# act.secrets
7980
act.secrets

gemfiles/test_gems.gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ group :development, :test do
3838
gem 'opentelemetry-propagator-b3'
3939
gem 'opentelemetry-exporter-otlp'
4040
gem 'opentelemetry-metrics-sdk'
41+
gem 'opentelemetry-exporter-otlp-metrics'
4142
end

lib/solarwinds_apm/api/transaction_name.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ module TransactionName
3838
# * Boolean
3939
#
4040
def set_transaction_name(custom_name = nil)
41+
42+
otel_config = SolarWindsAPM::OTelNativeConfig if defined? SolarWindsAPM::OTelNativeConfig
43+
otel_config = SolarWindsAPM::OTelConfig if defined? SolarWindsAPM::OTelConfig
44+
4145
status = true
42-
if ENV.fetch('SW_APM_ENABLED', 'true') == 'false' ||
43-
SolarWindsAPM::Context.toString == '99-00000000000000000000000000000000-0000000000000000-00'
46+
if ENV.fetch('SW_APM_ENABLED', 'true') == 'false'
47+
# check noop mode for otel ruby
48+
# || SolarWindsAPM::Context.toString == '99-00000000000000000000000000000000-0000000000000000-00'
4449
# library disabled or noop, just log and skip work.
4550
# TODO: can we have a single indicator that the API is in noop mode?
4651
SolarWindsAPM.logger.debug { "[#{name}/#{__method__}] SolarWindsAPM is in disabled or noop mode." }
@@ -49,13 +54,13 @@ def set_transaction_name(custom_name = nil)
4954
"[#{name}/#{__method__}] Set transaction name failed: custom_name is either nil or empty string."
5055
end
5156
status = false
52-
elsif SolarWindsAPM::OTelNativeConfig[:metrics_processor].nil?
57+
elsif otel_config[:metrics_processor].nil?
5358
SolarWindsAPM.logger.warn do
5459
"[#{name}/#{__method__}] Set transaction name failed: Solarwinds processor is missing."
5560
end
5661
status = false
5762
else
58-
solarwinds_processor = SolarWindsAPM::OTelNativeConfig[:metrics_processor]
63+
solarwinds_processor = otel_config[:metrics_processor]
5964
current_span = ::OpenTelemetry::Trace.current_span
6065

6166
if current_span.context.valid?

lib/solarwinds_apm/opentelemetry/otlp_processor.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ def initialize(txn_manager)
2020
super(txn_manager)
2121
@meters = init_meters
2222
@metrics = init_metrics
23-
@mutex = Mutex.new
2423
end
2524

2625
# @param [Span] span the (mutable) {Span} that just started.
@@ -41,6 +40,12 @@ def on_start(span, parent_context)
4140
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
4241
end
4342

43+
def on_finishing(span)
44+
transaction_name = calculate_transaction_names(span)
45+
span.set_attribute(SW_TRANSACTION_NAME, transaction_name)
46+
@txn_manager.delete_root_context_h(span.context.hex_trace_id)
47+
end
48+
4449
# @param [Span] span the (immutable) {Span} that just ended.
4550
def on_finish(span)
4651
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }
@@ -53,14 +58,6 @@ def on_finish(span)
5358
reader.pull if reader.respond_to? :pull
5459
end
5560

56-
transaction_name = calculate_transaction_names(span)
57-
@mutex.synchronize do
58-
mutable_attributes = span.attributes.dup
59-
mutable_attributes[SW_TRANSACTION_NAME] = transaction_name
60-
span.instance_variable_set(:@attributes, mutable_attributes.freeze)
61-
end
62-
@txn_manager.delete_root_context_h(span.context.hex_trace_id)
63-
6461
SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
6562
rescue StandardError => e
6663
SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }

lib/solarwinds_apm/sampling/oboe_sampler.rb

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@ class OboeSampler
2222
DICE_SCALE = 1_000_000
2323

2424
OTEL_SAMPLING_DECISION = ::OpenTelemetry::SDK::Trace::Samplers::Decision
25-
OTEL_SAMPLING_RESULT = ::OpenTelemetry::SDK::Trace::Samplers::Result
26-
27-
DEFAULT_TRACESTATE = ::OpenTelemetry::Trace::Tracestate::DEFAULT
25+
OTEL_SAMPLING_RESULT = ::OpenTelemetry::SDK::Trace::Samplers::Result
26+
DEFAULT_TRACESTATE = ::OpenTelemetry::Trace::Tracestate::DEFAULT
2827

2928
def initialize(logger)
3029
@logger = logger
@@ -84,8 +83,8 @@ def should_sample?(params)
8483

8584
# this validate_signature is the function from trace_options file
8685
sample_state.trace_options.response.auth = TraceOptions.validate_signature(
87-
sample_state.header['X-Trace-Options'],
88-
sample_state.header['X-Trace-Options-Signature'],
86+
sample_state.headers['X-Trace-Options'],
87+
sample_state.headers['X-Trace-Options-Signature'],
8988
sample_state.settings['signature_key'],
9089
sample_state.trace_options.timestamp
9190
)
@@ -186,7 +185,7 @@ def parent_based_algo(sample_state)
186185
@logger.debug { 'SAMPLE_THROUGH_ALWAYS is set; parent-based sampling' }
187186

188187
flags = sample_state.trace_state[-2, 2].to_i(16)
189-
sampled = flags & OpenTelemetry::Trace::TraceFlags::SAMPLED != 0
188+
sampled = flags & (OpenTelemetry::Trace::TraceFlags::SAMPLED.sampled? ? 1 : 0)
190189

191190
if sampled
192191
@logger.debug { 'parent is sampled; record and sample' }
@@ -247,7 +246,7 @@ def trigger_trace_algo(sample_state)
247246
end
248247

249248
def dice_roll_algo(sample_state)
250-
dice = Dice.new(rate: sample_state.settings[:sample_rate], scale: DICE_SCALE)
249+
dice = SolarWindsAPM::Dice.new(rate: sample_state.settings[:sample_rate], scale: DICE_SCALE)
251250
sample_state.attributes[SAMPLE_RATE_ATTRIBUTE] = dice.rate
252251
sample_state.attributes[SAMPLE_SOURCE_ATTRIBUTE] = sample_state.settings[:sample_source]
253252

@@ -283,10 +282,10 @@ def dice_roll_algo(sample_state)
283282
def disabled_algo(sample_state)
284283
if sample_state.trace_options&.trigger_trace
285284
@logger.debug { 'trigger trace requested but tracing disabled' }
286-
sample_state.trace_options.response.trigger_trace = TriggerTrace.TRACING_DISABLED
285+
sample_state.trace_options.response.trigger_trace = TriggerTrace::TRACING_DISABLED
287286
end
288287

289-
if sample_state.settings[:flags].nobits?(Flags.SAMPLE_THROUGH_ALWAYS)
288+
if sample_state.settings[:flags].nobits?(Flags::SAMPLE_THROUGH_ALWAYS)
290289
@logger.debug { "SAMPLE_THROUGH_ALWAYS is unset; don't record" }
291290
sample_state.decision = OTEL_SAMPLING_DECISION::DROP
292291
else

lib/solarwinds_apm/sampling/sampler.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ def update_settings(settings)
106106
end
107107

108108
def http_span_metadata(kind, attributes)
109-
puts "kind: #{kind}"
110109
return { http: false } unless kind == ::OpenTelemetry::Trace::SpanKind::SERVER &&
111110
(attributes.key?(ATTR_HTTP_REQUEST_METHOD) || attributes.key?(ATTR_HTTP_METHOD))
112111

lib/solarwinds_apm/sampling/sampling_constants.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ module Flags
7171
end
7272

7373
module TracingMode
74-
ALWAYS = Flags::SAMPLE_START || Flags::SAMPLE_THROUGH_ALWAYS
74+
ALWAYS = Flags::SAMPLE_START | Flags::SAMPLE_THROUGH_ALWAYS
7575
NEVER = 0x0
7676
end
7777

lib/solarwinds_apm/sampling/sampling_patch.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,34 @@ def export(metrics, timeout: nil)
1111
end
1212
end
1313

14+
module SolarWindsAPM
15+
module Span
16+
module Patch
17+
def finish(end_timestamp: nil)
18+
@mutex.synchronize do
19+
if @ended
20+
OpenTelemetry.logger.warn('Calling finish on an ended Span.')
21+
return self
22+
end
23+
end
24+
25+
@span_processors.each do |processor|
26+
processor.on_finishing(self) if processor.respond_to?(:on_finishing)
27+
end
28+
29+
@mutex.synchronize do
30+
@end_timestamp = relative_timestamp(end_timestamp)
31+
@attributes = validated_attributes(@attributes).freeze
32+
@events.freeze
33+
@links.freeze
34+
@ended = true
35+
end
36+
@span_processors.each { |processor| processor.on_finish(self) }
37+
self
38+
end
39+
end
40+
end
41+
end
42+
1443
OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.prepend(SolarWindsAPM::MetricsExporter::Patch)
44+
OpenTelemetry::SDK::Trace::Span.prepend(SolarWindsAPM::Span::Patch)

test/sampling/http_sampler_test.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2025 SolarWinds, LLC.
4+
# All rights reserved.
5+
6+
# BUNDLE_GEMFILE=gemfiles/unit.gemfile bundle exec ruby -I test test/sampling/http_sampler_test.rb
7+
8+
require 'minitest_helper'
9+
require 'opentelemetry-metrics-sdk'
10+
require 'opentelemetry-exporter-otlp-metrics'
11+
require './lib/solarwinds_apm/sampling'
12+
13+
describe 'HttpSampler' do
14+
15+
before do
16+
@config = {
17+
:collector => "https://#{ENV.fetch('SW_APM_COLLECTOR', 'apm.collector.cloud.solarwinds.com')}:443",
18+
:service => 'bbbbbbbb',
19+
:headers => "Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
20+
:tracing_mode => true,
21+
:trigger_trace_enabled => true,
22+
:transaction_settings => true}
23+
end
24+
25+
describe "valid service key" do
26+
before do
27+
new_config = @config.dup
28+
@sampler = SolarWindsAPM::HttpSampler.new(new_config)
29+
# otel.reset(trace: { sampler: @sampler })
30+
@sampler.wait_until_ready(1000)
31+
end
32+
33+
it "samples created spans" do
34+
tracer = ::OpenTelemetry.tracer_provider.tracer("test")
35+
tracer.in_span("test") do |span|
36+
assert span.recording?, "Expected span to be recording"
37+
end
38+
39+
span = otel.spans.first
40+
refute_nil span, "Expected span to be present"
41+
assert_includes span.attributes.keys, "SampleRate"
42+
assert_includes span.attributes.keys, "SampleSource"
43+
assert_includes span.attributes.keys, "BucketCapacity"
44+
assert_includes span.attributes.keys, "BucketRate"
45+
end
46+
end
47+
48+
describe "invalid service key" do
49+
before do
50+
new_config = @config.merge(headers: { "Authorization" => "Bearer oh-no" })
51+
@sampler = SolarWindsAPM::HttpSampler.new(new_config)
52+
# otel.reset(trace: { sampler: @sampler })
53+
@sampler.wait_until_ready(1000)
54+
end
55+
56+
it "does not sample created spans" do
57+
tracer = ::OpenTelemetry.tracer_provider.tracer("test")
58+
tracer.in_span("test") do |span|
59+
refute span.recording?, "Expected span to not be recording"
60+
end
61+
62+
spans = otel.spans
63+
assert_empty spans, "Expected no spans to be created"
64+
end
65+
end
66+
67+
describe "invalid collector" do
68+
before do
69+
new_config = @config.merge(collector: URI("https://collector.invalid"))
70+
@sampler = SolarWindsAPM::HttpSampler.new(new_config)
71+
# otel.reset(trace: { sampler: @sampler })
72+
@sampler.wait_until_ready(1000)
73+
end
74+
75+
it "does not sample created spans" do
76+
tracer = ::OpenTelemetry.tracer_provider.tracer("test")
77+
tracer.in_span("test") do |span|
78+
refute span.recording?, "Expected span to not be recording"
79+
end
80+
81+
spans = otel.spans
82+
assert_empty spans, "Expected no spans to be created"
83+
end
84+
85+
it "retries with backoff" do
86+
sleep 1 # Simulating backoff delay
87+
end
88+
end
89+
end

0 commit comments

Comments
 (0)