solarwinds_apm 6.1.1 → 7.0.0.prev1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +53 -4
  3. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +0 -4
  4. data/lib/solarwinds_apm/api/current_trace_info.rb +10 -6
  5. data/lib/solarwinds_apm/api/custom_instrumentation.rb +80 -0
  6. data/lib/solarwinds_apm/api/custom_metrics.rb +8 -25
  7. data/lib/solarwinds_apm/api/tracing.rb +12 -27
  8. data/lib/solarwinds_apm/api/transaction_name.rb +6 -10
  9. data/lib/solarwinds_apm/api.rb +2 -0
  10. data/lib/solarwinds_apm/config.rb +1 -1
  11. data/lib/solarwinds_apm/constants.rb +1 -0
  12. data/lib/solarwinds_apm/noop/api.rb +5 -2
  13. data/lib/solarwinds_apm/noop.rb +0 -24
  14. data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +90 -69
  15. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +0 -2
  16. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +5 -4
  17. data/lib/solarwinds_apm/opentelemetry.rb +5 -7
  18. data/lib/solarwinds_apm/otel_native_config.rb +177 -0
  19. data/lib/solarwinds_apm/patch/README.md +15 -0
  20. data/lib/solarwinds_apm/patch/tag_sql/sw_dbo_utils.rb +35 -0
  21. data/lib/solarwinds_apm/patch/tag_sql/sw_mysql2_patch.rb +1 -12
  22. data/lib/solarwinds_apm/patch/tag_sql/sw_pg_patch.rb +39 -0
  23. data/lib/solarwinds_apm/patch/tag_sql_patch.rb +2 -0
  24. data/lib/solarwinds_apm/{noop/metadata.rb → sampling/dice.rb} +19 -17
  25. data/lib/solarwinds_apm/sampling/http_sampler.rb +87 -0
  26. data/lib/solarwinds_apm/sampling/json_sampler.rb +52 -0
  27. data/lib/solarwinds_apm/sampling/metrics.rb +38 -0
  28. data/lib/solarwinds_apm/sampling/oboe_sampler.rb +348 -0
  29. data/lib/solarwinds_apm/sampling/sampler.rb +197 -0
  30. data/lib/solarwinds_apm/sampling/sampling_constants.rb +127 -0
  31. data/lib/solarwinds_apm/sampling/sampling_patch.rb +49 -0
  32. data/lib/solarwinds_apm/sampling/setting_example.txt +1 -0
  33. data/lib/solarwinds_apm/{noop/context.rb → sampling/settings.rb} +14 -25
  34. data/lib/solarwinds_apm/sampling/token_bucket.rb +126 -0
  35. data/lib/solarwinds_apm/sampling/trace_options.rb +100 -0
  36. data/lib/solarwinds_apm/{patch.rb → sampling.rb} +20 -4
  37. data/lib/solarwinds_apm/{noop/span.rb → support/aws_resource_detector.rb} +5 -18
  38. data/lib/solarwinds_apm/support/logger_formatter.rb +1 -1
  39. data/lib/solarwinds_apm/support/logging_log_event.rb +1 -1
  40. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +1 -1
  41. data/lib/solarwinds_apm/support/otlp_endpoint.rb +99 -0
  42. data/lib/solarwinds_apm/support/resource_detector/aws/beanstalk.rb +51 -0
  43. data/lib/solarwinds_apm/support/resource_detector/aws/ec2.rb +145 -0
  44. data/lib/solarwinds_apm/support/resource_detector/aws/ecs.rb +173 -0
  45. data/lib/solarwinds_apm/support/resource_detector/aws/eks.rb +174 -0
  46. data/lib/solarwinds_apm/support/resource_detector/aws/lambda.rb +66 -0
  47. data/lib/solarwinds_apm/support/resource_detector.rb +192 -0
  48. data/lib/solarwinds_apm/support/service_key_checker.rb +12 -6
  49. data/lib/solarwinds_apm/support/transaction_settings.rb +6 -0
  50. data/lib/solarwinds_apm/support/txn_name_manager.rb +54 -9
  51. data/lib/solarwinds_apm/support/utils.rb +9 -0
  52. data/lib/solarwinds_apm/support.rb +3 -4
  53. data/lib/solarwinds_apm/version.rb +4 -4
  54. data/lib/solarwinds_apm.rb +27 -73
  55. metadata +105 -50
  56. data/ext/oboe_metal/extconf.rb +0 -168
  57. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +0 -1
  58. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +0 -1
  59. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +0 -1
  60. data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +0 -1
  61. data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +0 -1
  62. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +0 -1
  63. data/ext/oboe_metal/src/VERSION +0 -1
  64. data/ext/oboe_metal/src/bson/bson.h +0 -220
  65. data/ext/oboe_metal/src/bson/platform_hacks.h +0 -91
  66. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -18
  67. data/ext/oboe_metal/src/oboe.h +0 -930
  68. data/ext/oboe_metal/src/oboe_api.cpp +0 -793
  69. data/ext/oboe_metal/src/oboe_api.h +0 -621
  70. data/ext/oboe_metal/src/oboe_debug.h +0 -17
  71. data/ext/oboe_metal/src/oboe_swig_wrap.cc +0 -10954
  72. data/lib/oboe_metal.rb +0 -187
  73. data/lib/solarwinds_apm/cert/star.appoptics.com.issuer.crt +0 -24
  74. data/lib/solarwinds_apm/oboe_init_options.rb +0 -222
  75. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +0 -239
  76. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +0 -174
  77. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +0 -333
  78. data/lib/solarwinds_apm/otel_config.rb +0 -174
  79. data/lib/solarwinds_apm/otel_lambda_config.rb +0 -56
  80. data/lib/solarwinds_apm/patch/dummy_patch.rb +0 -12
  81. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +0 -33
  82. data/lib/solarwinds_apm/support/support_report.rb +0 -99
  83. data/lib/solarwinds_apm/support/swomarginalia/LICENSE +0 -20
  84. data/lib/solarwinds_apm/support/swomarginalia/README.md +0 -46
  85. data/lib/solarwinds_apm/support/swomarginalia/comment.rb +0 -206
  86. data/lib/solarwinds_apm/support/swomarginalia/formatter.rb +0 -20
  87. data/lib/solarwinds_apm/support/swomarginalia/load_swomarginalia.rb +0 -55
  88. data/lib/solarwinds_apm/support/swomarginalia/railtie.rb +0 -24
  89. data/lib/solarwinds_apm/support/swomarginalia/swomarginalia.rb +0 -89
  90. data/lib/solarwinds_apm/support/transaction_cache.rb +0 -57
  91. data/lib/solarwinds_apm/support/x_trace_options.rb +0 -138
@@ -8,41 +8,60 @@
8
8
 
9
9
  module SolarWindsAPM
10
10
  module OpenTelemetry
11
- # reference: OpenTelemetry::SDK::Trace::SpanProcessor; inheritance: SolarWindsProcessor
12
- class OTLPProcessor < SolarWindsProcessor
13
- attr_accessor :description
14
-
15
- def initialize
16
- super(nil)
17
- @meters = init_meters
18
- @metrics = init_metrics
11
+ # reference: OpenTelemetry::SDK::Trace::SpanProcessor
12
+ class OTLPProcessor
13
+ attr_reader :txn_manager
14
+
15
+ SW_TRANSACTION_NAME = 'sw.transaction'
16
+ SW_IS_ENTRY_SPAN = 'sw.is_entry_span'
17
+ SW_IS_ERROR = 'sw.is_error'
18
+
19
+ HTTP_METHOD = 'http.method'
20
+ HTTP_ROUTE = 'http.route'
21
+ HTTP_STATUS_CODE = 'http.status_code'
22
+ HTTP_URL = 'http.url'
23
+
24
+ INVALID_HTTP_STATUS_CODE = 0
25
+
26
+ def initialize(txn_manager)
27
+ @txn_manager = txn_manager
28
+ @meters = { 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics') }
29
+ @metrics = { response_time: @meters['sw.apm.request.metrics'].create_histogram('trace.service.response_time', unit: 'ms', description: 'Duration of each entry span for the service, typically meaning the time taken to process an inbound request.') }
30
+ @transaction_name = nil
19
31
  end
20
32
 
21
- # @param [Span] span the {Span} that just started.
22
- # @param [Context] parent_context the
23
- # started span.
33
+ # @param [Span] span the (mutable) {Span} that just started.
34
+ # @param [Context] parent_context of the started span.
24
35
  def on_start(span, parent_context)
25
36
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_start span: #{span.to_span_data.inspect}" }
26
37
 
27
38
  return if non_entry_span(parent_context: parent_context)
28
39
 
29
- span.add_attributes(span_attributes(span))
30
- span.add_attributes({ 'sw.is_entry_span' => true })
40
+ trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
41
+ @txn_manager&.set_root_context_h(span.context.hex_trace_id, "#{span.context.hex_span_id}-#{trace_flags}")
42
+ span.add_attributes({ SW_IS_ENTRY_SPAN => true })
31
43
  rescue StandardError => e
32
44
  SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] processor on_start error: #{e.message}" }
33
45
  end
34
46
 
35
- # @param [Span] span the {Span} that just ended.
47
+ def on_finishing(span)
48
+ @transaction_name = calculate_transaction_names(span)
49
+ span.set_attribute(SW_TRANSACTION_NAME, @transaction_name)
50
+ @txn_manager.delete_root_context_h(span.context.hex_trace_id)
51
+ end
52
+
53
+ # @param [Span] span the (immutable) {Span} that just ended.
36
54
  def on_finish(span)
37
55
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish span: #{span.to_span_data.inspect}" }
38
-
39
- # return if span is non-entry span
40
56
  return if non_entry_span(span: span)
41
57
 
42
58
  record_request_metrics(span)
43
- record_sampling_metrics
44
59
 
45
- ::OpenTelemetry.meter_provider.metric_readers.each(&:pull)
60
+ # pull should work on any instrument from oboe_sampler
61
+ ::OpenTelemetry.meter_provider.metric_readers.each do |reader|
62
+ reader.pull if reader.respond_to? :pull
63
+ end
64
+
46
65
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
47
66
  rescue StandardError => e
48
67
  SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
@@ -50,24 +69,10 @@ module SolarWindsAPM
50
69
 
51
70
  private
52
71
 
53
- # Create two meters for sampling and request count
54
- def init_meters
55
- {
56
- 'sw.apm.sampling.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.sampling.metrics'),
57
- 'sw.apm.request.metrics' => ::OpenTelemetry.meter_provider.meter('sw.apm.request.metrics')
58
- }
59
- end
60
-
61
- def span_attributes(span)
62
- span_attrs = { 'sw.transaction' => calculate_lambda_transaction_name(span) }
63
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_attrs: #{span_attrs.inspect}" }
64
- span_attrs
65
- end
66
-
67
72
  def meter_attributes(span)
68
73
  meter_attrs = {
69
- 'sw.is_error' => error?(span) == 1,
70
- 'sw.transaction' => calculate_lambda_transaction_name(span)
74
+ SW_IS_ERROR => error?(span) == 1,
75
+ SW_TRANSACTION_NAME => @transaction_name
71
76
  }
72
77
 
73
78
  if span_http?(span)
@@ -76,26 +81,33 @@ module SolarWindsAPM
76
81
  meter_attrs['http.method'] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
77
82
  end
78
83
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] meter_attrs: #{meter_attrs.inspect}" }
84
+ meter_attrs.compact!
79
85
  meter_attrs
80
86
  end
81
87
 
82
- def calculate_lambda_transaction_name(span)
83
- (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span.name || 'unknown').slice(0, 255)
88
+ def calculate_lambda_transaction_name(span_name)
89
+ (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span_name || 'unknown').slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
84
90
  end
85
91
 
86
- def init_metrics
87
- request_meter = @meters['sw.apm.request.metrics']
88
- sampling_meter = @meters['sw.apm.sampling.metrics']
89
-
90
- metrics = {}
91
- metrics[:response_time] = request_meter.create_histogram('trace.service.response_time', unit: 'ms', description: 'measures the duration of an inbound HTTP request')
92
- metrics[:tracecount] = sampling_meter.create_counter('trace.service.tracecount')
93
- metrics[:samplecount] = sampling_meter.create_counter('trace.service.samplecount')
94
- metrics[:request_count] = sampling_meter.create_counter('trace.service.request_count')
95
- metrics[:toex_count] = sampling_meter.create_counter('trace.service.tokenbucket_exhaustion_count')
96
- metrics[:through_count] = sampling_meter.create_counter('trace.service.through_trace_count')
97
- metrics[:tt_count] = sampling_meter.create_counter('trace.service.triggered_trace_count')
98
- metrics
92
+ # Get trans_name and url_tran of this span instance.
93
+ # Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
94
+ def calculate_transaction_names(span)
95
+ return calculate_lambda_transaction_name(span.name) if SolarWindsAPM::Utils.determine_lambda
96
+
97
+ trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
98
+ trans_name = @txn_manager.get(trace_span_id)
99
+ if trans_name
100
+ SolarWindsAPM.logger.debug do
101
+ "[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
102
+ end
103
+ @txn_manager.del(trace_span_id)
104
+ elsif !ENV['SW_APM_TRANSACTION_NAME'].to_s.empty?
105
+ trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
106
+ else
107
+ trans_name = span.attributes[HTTP_ROUTE] || nil
108
+ trans_name = span.name if trans_name.to_s.empty? && span.name
109
+ end
110
+ trans_name.to_s.slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
99
111
  end
100
112
 
101
113
  def record_request_metrics(span)
@@ -106,31 +118,40 @@ module SolarWindsAPM
106
118
  @metrics[:response_time].record(span_time, attributes: meter_attrs)
107
119
  end
108
120
 
109
- # oboe_api will return 0 in case of failed operation, and report 0 value
110
- def record_sampling_metrics
111
- _, trace_count = SolarWindsAPM.oboe_api.consumeTraceCount
112
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] trace_count: #{trace_count}" }
113
- @metrics[:tracecount].add(trace_count)
121
+ # Calculate span time in microseconds (us) using start and end time
122
+ # in nanoseconds (ns). OTel span start/end_time are optional.
123
+ def calculate_span_time(start_time: nil, end_time: nil)
124
+ return 0 if start_time.nil? || end_time.nil?
114
125
 
115
- _, sample_count = SolarWindsAPM.oboe_api.consumeSampleCount
116
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] sample_count: #{sample_count}" }
117
- @metrics[:samplecount].add(sample_count)
126
+ ((end_time.to_i - start_time.to_i) / 1e3).round
127
+ end
118
128
 
119
- _, request_count = SolarWindsAPM.oboe_api.consumeRequestCount
120
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] request_count: #{request_count}" }
121
- @metrics[:request_count].add(request_count)
129
+ # Calculate if this span instance has_error
130
+ # return [Integer]
131
+ def error?(span)
132
+ span.status.code == ::OpenTelemetry::Trace::Status::ERROR ? 1 : 0
133
+ end
122
134
 
123
- _, token_bucket_exhaustion_count = SolarWindsAPM.oboe_api.consumeTokenBucketExhaustionCount
124
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] tokenbucket_exhaustion_count: #{token_bucket_exhaustion_count}" }
125
- @metrics[:toex_count].add(token_bucket_exhaustion_count)
135
+ # This span from inbound HTTP request if from a SERVER by some http.method
136
+ def span_http?(span)
137
+ span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?
138
+ end
126
139
 
127
- _, through_trace_count = SolarWindsAPM.oboe_api.consumeThroughTraceCount
128
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] through_trace_count: #{through_trace_count}" }
129
- @metrics[:through_count].add(through_trace_count)
140
+ # Calculate HTTP status_code from span or default to UNAVAILABLE
141
+ # Something went wrong in OTel or instrumented service crashed early
142
+ # if no status_code in attributes of HTTP span
143
+ def get_http_status_code(span)
144
+ span.attributes[HTTP_STATUS_CODE] || INVALID_HTTP_STATUS_CODE
145
+ end
130
146
 
131
- _, triggered_trace_count = SolarWindsAPM.oboe_api.consumeTriggeredTraceCount
132
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] triggered_trace_count: #{triggered_trace_count}" }
133
- @metrics[:tt_count].add(triggered_trace_count)
147
+ # check if it's entry span based on no parent or parent is remote
148
+ def non_entry_span(span: nil, parent_context: nil)
149
+ if parent_context
150
+ parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
151
+ parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
152
+ elsif span
153
+ span.attributes['sw.is_entry_span'] != true
154
+ end
134
155
  end
135
156
  end
136
157
  end
@@ -51,8 +51,6 @@ module SolarWindsAPM
51
51
  # text map setter will be used.
52
52
  def inject(carrier, context: ::OpenTelemetry::Context.current,
53
53
  setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
54
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] inject context: #{context.inspect}" }
55
-
56
54
  span_context = ::OpenTelemetry::Trace.current_span(context)&.context
57
55
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context #{span_context.inspect}" }
58
56
  return unless span_context&.valid?
@@ -16,6 +16,7 @@ module SolarWindsAPM
16
16
  XTRACE_HEADER_NAME = 'x-trace'
17
17
  XTRACEOPTIONS_RESPONSE_HEADER_NAME = 'x-trace-options-response'
18
18
  INTL_SWO_EQUALS = '='
19
+ SW_XTRACEOPTIONS_RESPONSE_KEY = 'xtrace_options_response'
19
20
 
20
21
  private_constant \
21
22
  :HTTP_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, :XTRACE_HEADER_NAME,
@@ -36,14 +37,13 @@ module SolarWindsAPM
36
37
  setter: ::OpenTelemetry::Context::Propagation.text_map_setter)
37
38
  span_context = ::OpenTelemetry::Trace.current_span(context).context
38
39
 
39
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] context current_span: #{context.instance_variable_get(:@entries)&.values&.first.inspect}" }
40
40
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] span_context: #{span_context.inspect}" }
41
41
 
42
42
  return unless span_context&.valid?
43
43
 
44
44
  x_trace = Utils.traceparent_from_context(span_context)
45
45
  exposed_headers = [XTRACE_HEADER_NAME]
46
- xtraceoptions_response = recover_response_from_tracestate(span_context.tracestate)
46
+ xtraceoptions_response = recover_response_from_tracestate(span_context)
47
47
 
48
48
  SolarWindsAPM.logger.debug do
49
49
  "[#{self.class}/#{__method__}] x-trace: #{x_trace}; exposed headers: #{exposed_headers.inspect}; x-trace-options-response: #{xtraceoptions_response}"
@@ -69,11 +69,12 @@ module SolarWindsAPM
69
69
  private
70
70
 
71
71
  # sw_xtraceoptions_response_key -> xtrace_options_response
72
- def recover_response_from_tracestate(tracestate)
73
- sanitized = tracestate.value(XTraceOptions.sw_xtraceoptions_response_key)
72
+ def recover_response_from_tracestate(span_context)
73
+ sanitized = span_context.tracestate.value(SW_XTRACEOPTIONS_RESPONSE_KEY)
74
74
  sanitized = '' if sanitized.nil?
75
75
  sanitized = sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_EQUALS_W3C_SANITIZED,
76
76
  SolarWindsAPM::Constants::INTL_SWO_EQUALS)
77
+ sanitized = sanitized.gsub(':', SolarWindsAPM::Constants::INTL_SWO_EQUALS)
77
78
  sanitized = sanitized.gsub(SolarWindsAPM::Constants::INTL_SWO_COMMA_W3C_SANITIZED,
78
79
  SolarWindsAPM::Constants::INTL_SWO_COMMA)
79
80
  SolarWindsAPM.logger.debug do
@@ -6,14 +6,12 @@
6
6
  #
7
7
  # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
8
 
9
- require 'opentelemetry/sdk'
10
- require 'opentelemetry/instrumentation/all'
11
-
12
- # TODO: in future, it should add opentelemetry-metrics-sdk and require it here
9
+ require 'opentelemetry-sdk'
10
+ require 'opentelemetry-metrics-sdk'
11
+ require 'opentelemetry-exporter-otlp'
12
+ require 'opentelemetry-exporter-otlp-metrics'
13
+ require 'opentelemetry-instrumentation-all'
13
14
 
14
15
  require_relative 'opentelemetry/solarwinds_propagator'
15
- require_relative 'opentelemetry/solarwinds_processor'
16
- require_relative 'opentelemetry/solarwinds_sampler'
17
- require_relative 'opentelemetry/solarwinds_exporter'
18
16
  require_relative 'opentelemetry/solarwinds_response_propagator'
19
17
  require_relative 'opentelemetry/otlp_processor'
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ # © 2023 SolarWinds Worldwide, LLC. All rights reserved.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at:http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
+
9
+ require_relative 'api'
10
+ require_relative 'support'
11
+ require_relative 'opentelemetry'
12
+ require_relative 'sampling'
13
+
14
+ module SolarWindsAPM
15
+ # OTelNativeConfig module
16
+ module OTelNativeConfig
17
+ @@config = {}
18
+ @@config_map = {}
19
+ @@agent_enabled = false
20
+
21
+ RESOURCE_ATTRIBUTES = 'RESOURCE_ATTRIBUTES'
22
+
23
+ def self.initialize
24
+ return unless defined?(::OpenTelemetry::SDK::Configurator)
25
+
26
+ is_lambda = SolarWindsAPM::Utils.determine_lambda
27
+
28
+ ENV['OTEL_TRACES_EXPORTER'] = ENV['OTEL_TRACES_EXPORTER'].to_s.split(',').tap { |e| e << 'otlp' unless e.include?('otlp') }.join(',')
29
+
30
+ # add response propagator to rack instrumentation
31
+ resolve_response_propagator
32
+
33
+ # dbo: traceparent injection as sql comments
34
+ require_relative 'patch/tag_sql_patch' if SolarWindsAPM::Config[:tag_sql]
35
+
36
+ # endpoint and service_key for non-lambda
37
+ otlp_endpoint = nil
38
+ unless is_lambda
39
+ otlp_endpoint = SolarWindsAPM::OTLPEndPoint.new
40
+ otlp_endpoint.config_otlp_token_and_endpoint
41
+ return if otlp_endpoint.token.nil?
42
+ end
43
+
44
+ ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.apm.version=#{SolarWindsAPM::Version::STRING},sw.data.module=apm,service.name=#{ENV.fetch('OTEL_SERVICE_NAME', nil)}," + ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s
45
+
46
+ # resource attributes
47
+ mandatory_resource = SolarWindsAPM::ResourceDetector.detect
48
+ additional_attributes = @@config_map[RESOURCE_ATTRIBUTES]
49
+ if additional_attributes
50
+ if additional_attributes.instance_of?(::OpenTelemetry::SDK::Resources::Resource)
51
+ final_attributes = mandatory_resource.merge(additional_attributes)
52
+ elsif additional_attributes.instance_of?(Hash)
53
+ final_attributes = mandatory_resource.merge(::OpenTelemetry::SDK::Resources::Resource.create(additional_attributes))
54
+ end
55
+ @@config_map.delete(RESOURCE_ATTRIBUTES)
56
+ else
57
+ final_attributes = mandatory_resource.merge({})
58
+ end
59
+
60
+ # set gzip compression
61
+ %w[TRACES METRICS LOGS].each do |signal|
62
+ ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"] = 'gzip' if ENV["OTEL_EXPORTER_OTLP_#{signal}_COMPRESSION"].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_COMPRESSION'].to_s.empty?
63
+ end
64
+
65
+ # set delta temporality
66
+ ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'] = 'delta' if ENV['OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE'].to_s.empty?
67
+
68
+ # log level
69
+ if ENV['OTEL_LOG_LEVEL'].to_s.empty?
70
+ log_level = (ENV['SW_APM_DEBUG_LEVEL'] || SolarWindsAPM::Config[:debug_level] || 3).to_i
71
+ ENV['OTEL_LOG_LEVEL'] = SolarWindsAPM::Config::SW_LOG_LEVEL_MAPPING.dig(log_level, :otel)
72
+ end
73
+
74
+ ::OpenTelemetry::SDK.configure do |c|
75
+ c.resource = final_attributes
76
+ c.use_all(@@config_map)
77
+ end
78
+
79
+ # append our propagators
80
+ ::OpenTelemetry.propagation.instance_variable_get(:@propagators).append(SolarWindsAPM::OpenTelemetry::SolarWindsPropagator::TextMapPropagator.new)
81
+
82
+ # add sw metrics processors (only record respone_time)
83
+ txn_manager = TxnNameManager.new
84
+ otlp_processor = SolarWindsAPM::OpenTelemetry::OTLPProcessor.new(txn_manager)
85
+
86
+ @@config[:metrics_processor] = otlp_processor
87
+ ::OpenTelemetry.tracer_provider.add_span_processor(otlp_processor)
88
+
89
+ # collector, service and headers are used for http sampler get settings
90
+ sampler_config = {
91
+ tracing_mode: SolarWindsAPM::Config[:tracing_mode],
92
+ trigger_trace_enabled: SolarWindsAPM::Config[:trigger_tracing_mode],
93
+ transaction_settings: SolarWindsAPM::Config[:transaction_settings]
94
+ }
95
+
96
+ unless otlp_endpoint.nil?
97
+ sampler_config.merge!({
98
+ collector: "https://#{ENV.fetch('SW_APM_COLLECTOR', 'apm.collector.cloud.solarwinds.com:443')}",
99
+ service: otlp_endpoint.service_name,
100
+ headers: "Bearer #{otlp_endpoint.token}"
101
+ })
102
+ end
103
+
104
+ sampler = is_lambda ? JsonSampler.new(sampler_config) : HttpSampler.new(sampler_config)
105
+
106
+ ::OpenTelemetry.tracer_provider.sampler = ::OpenTelemetry::SDK::Trace::Samplers.parent_based(
107
+ root: sampler,
108
+ remote_parent_sampled: sampler,
109
+ remote_parent_not_sampled: sampler
110
+ )
111
+
112
+ @@agent_enabled = true
113
+
114
+ nil
115
+ end
116
+
117
+ def self.[](key)
118
+ @@config[key.to_sym]
119
+ end
120
+
121
+ def self.agent_enabled
122
+ @@agent_enabled
123
+ end
124
+
125
+ def self.resolve_response_propagator
126
+ response_propagator = SolarWindsAPM::OpenTelemetry::SolarWindsResponsePropagator::TextMapPropagator.new
127
+ rack_setting = @@config_map['OpenTelemetry::Instrumentation::Rack']
128
+
129
+ if rack_setting
130
+ if rack_setting[:response_propagators].instance_of?(Array)
131
+ rack_setting[:response_propagators].append(response_propagator)
132
+ elsif rack_setting[:response_propagators].nil?
133
+ rack_setting[:response_propagators] = [response_propagator]
134
+ else
135
+ SolarWindsAPM.logger.warn do
136
+ "[#{name}/#{__method__}] Rack response propagator resolve failed. Provided type #{rack_setting[:response_propagators].class}, please provide Array e.g. [#{rack_setting[:response_propagators]}]"
137
+ end
138
+ end
139
+ else
140
+ @@config_map['OpenTelemetry::Instrumentation::Rack'] = { response_propagators: [response_propagator] }
141
+ end
142
+ end
143
+
144
+ #
145
+ # Allow initialize after set new value to SolarWindsAPM::Config[:key]=value
146
+ #
147
+ # Usage:
148
+ #
149
+ # Default using the use_all to load all instrumentation
150
+ # But with specific instrumentation disabled, use {:enabled: false} in config
151
+ # SolarWindsAPM::OTelNativeConfig.initialize_with_config do |config|
152
+ # config["OpenTelemetry::Instrumentation::Rack"] = {"a" => "b"}
153
+ # config["OpenTelemetry::Instrumentation::Dalli"] = {:enabled: false}
154
+ # config["RESOURCE_ATTRIBUTES"] = ::OpenTelemetry::Resource::Detector::GoogleCloudPlatform.detect
155
+ # end
156
+ #
157
+ def self.initialize_with_config
158
+ unless block_given?
159
+ SolarWindsAPM.logger.warn do
160
+ "[#{name}/#{__method__}] Block not given while doing in-code configuration. Agent disabled."
161
+ end
162
+ return
163
+ end
164
+
165
+ yield @@config_map
166
+
167
+ if @@config_map.empty?
168
+ SolarWindsAPM.logger.warn do
169
+ "[#{name}/#{__method__}] No configuration given for in-code configuration. Agent disabled."
170
+ end
171
+ return
172
+ end
173
+
174
+ initialize
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,15 @@
1
+ # Patch
2
+
3
+ ## Patch upstream code
4
+
5
+ This folder is for storing patch files that apply to upstream code.
6
+
7
+ For example, to patch otel ruby sdk `Registry`, and assume you have dummy_patch file that have module `SolarWindsAPM::Patch::DummyPatch`,
8
+
9
+ ```ruby
10
+ require_relative './patch/dummy_patch'
11
+
12
+ if defined? OpenTelemetry::Instrumentation::Registry && OpenTelemetry::Instrumentation::Registry::VERSION <= '0.3.0'
13
+ OpenTelemetry::Instrumentation::Registry.prepend(SolarWindsAPM::Patch::DummyPatch)
14
+ end
15
+ ```
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module SolarWindsAPM
8
+ module Patch
9
+ module TagSql
10
+ module SWODboUtils
11
+ def self.annotate_span_and_sql(sql)
12
+ return sql if sql.to_s.empty?
13
+
14
+ current_span = ::OpenTelemetry::Trace.current_span
15
+
16
+ annotated_sql = ''
17
+ if current_span.context.trace_flags.sampled?
18
+ traceparent = SolarWindsAPM::Utils.traceparent_from_context(current_span.context)
19
+ annotated_traceparent = "/*traceparent='#{traceparent}'*/"
20
+ current_span.add_attributes({ 'sw.query_tag' => annotated_traceparent })
21
+ annotated_sql = "#{sql} #{annotated_traceparent}"
22
+ else
23
+ annotated_sql = sql
24
+ end
25
+
26
+ SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] annotated_sql: #{annotated_sql}" }
27
+ annotated_sql
28
+ rescue StandardError => e
29
+ SolarWindsAPM.logger.error { "[#{self.class}/#{__method__}] Failed to annotated sql. Error: #{e.message}" }
30
+ sql
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -11,18 +11,7 @@ module SolarWindsAPM
11
11
  module TagSql
12
12
  module SWOMysql2Patch
13
13
  def query(sql, options = {})
14
- current_span = ::OpenTelemetry::Trace.current_span
15
-
16
- annotated_sql = ''
17
- if current_span.context.trace_flags.sampled?
18
- traceparent = SolarWindsAPM::Utils.traceparent_from_context(current_span.context)
19
- annotated_traceparent = "/*traceparent='#{traceparent}'*/"
20
- current_span.add_attributes({ 'sw.query_tag' => annotated_traceparent })
21
- annotated_sql = "#{sql} #{annotated_traceparent}"
22
- else
23
- annotated_sql = sql
24
- end
25
-
14
+ annotated_sql = ::SolarWindsAPM::Patch::TagSql::SWODboUtils.annotate_span_and_sql(sql)
26
15
  super(annotated_sql, options)
27
16
  end
28
17
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module SolarWindsAPM
8
+ module Patch
9
+ module TagSql
10
+ module SWOPgPatch
11
+ # We target operations covered by the upstream pg instrumentation.
12
+ # These are all alike in that they will have a SQL
13
+ # statement as the first parameter, and they are all
14
+ # non-prepared statement execute.
15
+ EXEC_ISH_METHODS = %i[
16
+ exec
17
+ query
18
+ sync_exec
19
+ async_exec
20
+ exec_params
21
+ async_exec_params
22
+ sync_exec_params
23
+ ].freeze
24
+
25
+ EXEC_ISH_METHODS.each do |method|
26
+ define_method method do |*args|
27
+ annotated_sql = ::SolarWindsAPM::Patch::TagSql::SWODboUtils.annotate_span_and_sql(args[0])
28
+ args[0] = annotated_sql
29
+ super(*args)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # need to prepend before pg instrumentation patch itself
38
+ # upstream instrumentation -> our patch -> original function
39
+ PG::Connection.prepend(SolarWindsAPM::Patch::TagSql::SWOPgPatch) if defined?(PG::Connection)
@@ -6,4 +6,6 @@
6
6
  #
7
7
  # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
8
 
9
+ require_relative 'tag_sql/sw_dbo_utils'
9
10
  require_relative 'tag_sql/sw_mysql2_patch'
11
+ require_relative 'tag_sql/sw_pg_patch'
@@ -6,27 +6,29 @@
6
6
  #
7
7
  # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
8
8
 
9
- ####
10
- # noop version of SolarWindsAPM::Metadata
11
- #
12
- module Oboe_metal # rubocop:disable Naming/ClassAndModuleCamelCase
13
- # Metadata
14
- class Metadata
15
- ##
16
- # noop version of :makeRandom
17
- #
18
- # needs to return an object that responds to :isValid
19
- #
20
- def self.makeRandom
21
- Metadata.new
9
+ # Dice is used in oboe_sampler diceRollAlgo
10
+ module SolarWindsAPM
11
+ class Dice
12
+ attr_reader :rate, :scale
13
+
14
+ def initialize(settings)
15
+ @scale = settings[:scale]
16
+ @rate = settings[:rate] || 0
17
+ end
18
+
19
+ def update(settings)
20
+ @scale = settings[:scale] if settings[:scale]
21
+ @rate = settings[:rate] if settings[:rate]
22
22
  end
23
23
 
24
- def self.fromString(*)
25
- Metadata.new
24
+ # return Boolean
25
+ def roll
26
+ (rand * @scale) < @rate
26
27
  end
27
28
 
28
- def isValid
29
- false
29
+ def rate=(rate)
30
+ # Math.max(0, Math.min(this.#scale, n))
31
+ @rate = rate.clamp(0, @scale)
30
32
  end
31
33
  end
32
34
  end