solarwinds_apm 6.1.2 → 7.0.0.prev2

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +1 -30
  4. data/lib/solarwinds_apm/api/current_trace_info.rb +10 -6
  5. data/lib/solarwinds_apm/api/custom_metrics.rb +8 -25
  6. data/lib/solarwinds_apm/api/tracing.rb +12 -27
  7. data/lib/solarwinds_apm/api/transaction_name.rb +6 -10
  8. data/lib/solarwinds_apm/config.rb +7 -1
  9. data/lib/solarwinds_apm/constants.rb +1 -0
  10. data/lib/solarwinds_apm/noop/api.rb +5 -2
  11. data/lib/solarwinds_apm/noop.rb +0 -24
  12. data/lib/solarwinds_apm/opentelemetry/otlp_processor.rb +116 -66
  13. data/lib/solarwinds_apm/opentelemetry/solarwinds_propagator.rb +0 -2
  14. data/lib/solarwinds_apm/opentelemetry/solarwinds_response_propagator.rb +5 -4
  15. data/lib/solarwinds_apm/opentelemetry.rb +5 -7
  16. data/lib/solarwinds_apm/otel_native_config.rb +180 -0
  17. data/lib/solarwinds_apm/patch/README.md +15 -0
  18. data/lib/solarwinds_apm/{noop/metadata.rb → sampling/dice.rb} +19 -17
  19. data/lib/solarwinds_apm/sampling/http_sampler.rb +87 -0
  20. data/lib/solarwinds_apm/sampling/json_sampler.rb +52 -0
  21. data/lib/solarwinds_apm/sampling/metrics.rb +38 -0
  22. data/lib/solarwinds_apm/sampling/oboe_sampler.rb +348 -0
  23. data/lib/solarwinds_apm/sampling/sampler.rb +197 -0
  24. data/lib/solarwinds_apm/sampling/sampling_constants.rb +127 -0
  25. data/lib/solarwinds_apm/sampling/sampling_patch.rb +49 -0
  26. data/lib/solarwinds_apm/sampling/setting_example.txt +1 -0
  27. data/lib/solarwinds_apm/{noop/context.rb → sampling/settings.rb} +14 -25
  28. data/lib/solarwinds_apm/sampling/token_bucket.rb +126 -0
  29. data/lib/solarwinds_apm/sampling/trace_options.rb +100 -0
  30. data/lib/solarwinds_apm/{patch.rb → sampling.rb} +20 -4
  31. data/lib/solarwinds_apm/support/logger_formatter.rb +1 -1
  32. data/lib/solarwinds_apm/support/logging_log_event.rb +1 -1
  33. data/lib/solarwinds_apm/support/lumberjack_formatter.rb +1 -1
  34. data/lib/solarwinds_apm/support/otlp_endpoint.rb +99 -0
  35. data/lib/solarwinds_apm/support/resource_detector.rb +192 -0
  36. data/lib/solarwinds_apm/support/service_key_checker.rb +12 -6
  37. data/lib/solarwinds_apm/support/transaction_settings.rb +6 -0
  38. data/lib/solarwinds_apm/support/txn_name_manager.rb +54 -9
  39. data/lib/solarwinds_apm/support/utils.rb +9 -0
  40. data/lib/solarwinds_apm/support.rb +2 -4
  41. data/lib/solarwinds_apm/version.rb +4 -4
  42. data/lib/solarwinds_apm.rb +27 -73
  43. metadata +107 -40
  44. data/ext/oboe_metal/extconf.rb +0 -168
  45. data/ext/oboe_metal/lib/liboboe-1.0-aarch64.so.sha256 +0 -1
  46. data/ext/oboe_metal/lib/liboboe-1.0-alpine-aarch64.so.sha256 +0 -1
  47. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.sha256 +0 -1
  48. data/ext/oboe_metal/lib/liboboe-1.0-lambda-aarch64.so.sha256 +0 -1
  49. data/ext/oboe_metal/lib/liboboe-1.0-lambda-x86_64.so.sha256 +0 -1
  50. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.sha256 +0 -1
  51. data/ext/oboe_metal/src/VERSION +0 -1
  52. data/ext/oboe_metal/src/bson/bson.h +0 -220
  53. data/ext/oboe_metal/src/bson/platform_hacks.h +0 -91
  54. data/ext/oboe_metal/src/init_solarwinds_apm.cc +0 -18
  55. data/ext/oboe_metal/src/oboe.h +0 -930
  56. data/ext/oboe_metal/src/oboe_api.cpp +0 -793
  57. data/ext/oboe_metal/src/oboe_api.h +0 -621
  58. data/ext/oboe_metal/src/oboe_debug.h +0 -17
  59. data/ext/oboe_metal/src/oboe_swig_wrap.cc +0 -11045
  60. data/lib/oboe_metal.rb +0 -187
  61. data/lib/solarwinds_apm/cert/star.appoptics.com.issuer.crt +0 -24
  62. data/lib/solarwinds_apm/noop/span.rb +0 -25
  63. data/lib/solarwinds_apm/oboe_init_options.rb +0 -222
  64. data/lib/solarwinds_apm/opentelemetry/solarwinds_exporter.rb +0 -239
  65. data/lib/solarwinds_apm/opentelemetry/solarwinds_processor.rb +0 -174
  66. data/lib/solarwinds_apm/opentelemetry/solarwinds_sampler.rb +0 -333
  67. data/lib/solarwinds_apm/otel_config.rb +0 -174
  68. data/lib/solarwinds_apm/otel_lambda_config.rb +0 -56
  69. data/lib/solarwinds_apm/patch/dummy_patch.rb +0 -12
  70. data/lib/solarwinds_apm/support/oboe_tracing_mode.rb +0 -33
  71. data/lib/solarwinds_apm/support/support_report.rb +0 -99
  72. data/lib/solarwinds_apm/support/transaction_cache.rb +0 -57
  73. data/lib/solarwinds_apm/support/x_trace_options.rb +0 -138
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73f2befcea2c555995f79719d36c68d2448ae31f56656741d5c6e8d49e61963b
4
- data.tar.gz: 4ba3986285dcda8adc5629c2e08b5221638928d06733dd6fc0c009379b97ab4b
3
+ metadata.gz: b697fa83936187a865eb976aa169f2ce33749b479a439b36d4d46754f0252baf
4
+ data.tar.gz: 774462b35e16d0474c88575f751f763d6d3abf5ab463487050287c4097ab66aa
5
5
  SHA512:
6
- metadata.gz: 777aebc61cfb1f6776f0640f9b0cbd6005c5c375989a046809ce4f97551a8f2925154496eeef798726ac2242641646bb590c48d23caf71165473b0ac0cfa6b52
7
- data.tar.gz: c244cb0d3c1252cbcb85f3381cc82195ca8d59bf0ea4991f2c41f89a49d29a92efcf089ee35ae6274614493bd7e89d7ecb5851ec876a572d24fc5015ec7d5625
6
+ metadata.gz: '00109285994d22a3a8c871dafd6cdfd8dd8ff482366c18dee94936cf3a79bc9c30e1fe2eab867eeb54771b820cac9d71fb22be0669be397eea70bb11efb54799'
7
+ data.tar.gz: f2b8a360c720b2f2fcf91a1073ad47c6dabe0cf0c63d724ea00dd7cf95145873537ec71dab9ffc52b2f11201dba26b4805c17b45c59caae436214cadf2c03f44
data/README.md CHANGED
@@ -89,6 +89,7 @@ add_tracer :method_name, 'custom_span_name', { attributes: { 'any' => 'attribute
89
89
  For example, if you want to instrument class or instance method `create_session` inside an application controller:
90
90
 
91
91
  To instrument instance method
92
+
92
93
  ```ruby
93
94
  class SessionsController < ApplicationController
94
95
  include SolarWindsAPM::API::Tracer
@@ -107,6 +108,7 @@ end
107
108
  ```
108
109
 
109
110
  To instrument class method
111
+
110
112
  ```ruby
111
113
  class SessionsController < ApplicationController
112
114
  def create
@@ -142,10 +144,10 @@ trace.trace_flags # 01
142
144
 
143
145
  On startup, this library initializes and maintains a connection to a SolarWinds Observability collector, and receives settings used for making tracing decisions. This process can take up to a few seconds depending on the connection. If the application receives requests before initialization has completed, these requests will not be traced. While this is not critical for long-running server processes, it might be a problem for short-running apps such as cron jobs or CLI apps.
144
146
 
145
- A call to the `solarwinds_ready` method allows the application to block until initialization has completed and the library is ready for tracing. The method accepts an optional timeout parameter in milliseconds.
147
+ A call to the `solarwinds_ready?` method allows the application to block until initialization has completed and the library is ready for tracing. The method accepts an optional timeout parameter in milliseconds.
146
148
 
147
149
  ```ruby
148
- SolarWindsAPM::API.solarwinds_ready(wait_milliseconds=3000)
150
+ SolarWindsAPM::API.solarwinds_ready?(wait_milliseconds=3000)
149
151
  ```
150
152
 
151
153
  #### Set a Custom Transaction Name
@@ -156,7 +158,7 @@ By default, transaction names are constructed based on attributes such as the re
156
158
  result = SolarWindsAPM::API.set_transaction_name('my-custom-trace-name')
157
159
  ```
158
160
 
159
- #### Send Custom Metrics
161
+ #### Send Custom Metrics (Depreciated)
160
162
 
161
163
  Service metrics are automatically collected by this library. In addition, the following methods support sending two types of custom metrics:
162
164
 
@@ -16,8 +16,7 @@
16
16
 
17
17
  if defined?(SolarWindsAPM::Config)
18
18
 
19
- # :service_key, :hostname_alias, :http_proxy, and :debug_level
20
- # are startup settings and can't be changed afterwards.
19
+ # :service_key and :debug_level are startup settings and can't be changed afterwards.
21
20
 
22
21
  #
23
22
  # Set SW_APM_SERVICE_KEY
@@ -29,34 +28,6 @@ if defined?(SolarWindsAPM::Config)
29
28
  #
30
29
  # SolarWindsAPM::Config[:service_key] = '0123456789abcde0123456789abcde0123456789abcde0123456789abcde1234:my_service'
31
30
 
32
- #
33
- # Set SW_APM_HOSTNAME_ALIAS
34
- # This setting will be overridden if SW_APM_HOSTNAME_ALIAS is set as an environment variable
35
- #
36
- # SolarWindsAPM::Config[:hostname_alias] = 'alias_name'
37
-
38
- #
39
- # Set Proxy for SolarWinds
40
- # This setting will be overridden if SW_APM_PROXY is set as an environment variable.
41
- #
42
- # Please configure http_proxy if a proxy needs to be used to communicate with
43
- # the SolarWinds APM collector.
44
- # The format should either be http://<proxyHost>:<proxyPort> for a proxy
45
- # server that does not require authentication, or
46
- # http://<username>:<password>@<proxyHost>:<proxyPort> for a proxy server that
47
- # requires basic authentication.
48
- #
49
- # Note that while HTTP is the only type of connection supported, the traffic
50
- # to SolarWinds is still encrypted using SSL/TLS.
51
- #
52
- # It is recommended to configure the proxy in this file or as SW_APM_PROXY
53
- # environment variable. However, the agent's underlying network library will
54
- # use a system-wide proxy defined in the environment variables grpc_proxy,
55
- # https_proxy or http_proxy if no SolarWindsAPM-specific configuration is set.
56
- # Please refer to gRPC environment variables for more information.
57
- #
58
- # SolarWindsAPM::Config[:http_proxy] = http://<proxyHost>:<proxyPort>
59
-
60
31
  #
61
32
  # Set SW_APM_DEBUG_LEVEL
62
33
  # This setting will be overridden if SW_APM_DEBUG_LEVEL is set as an environment variable.
@@ -111,12 +111,16 @@ module SolarWindsAPM
111
111
  private
112
112
 
113
113
  def current_span
114
- span = ::OpenTelemetry::Trace.current_span if defined?(::OpenTelemetry::Trace)
115
- trace_id = span.context.hex_trace_id
116
- span_id = span.context.hex_span_id
117
- trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
118
- tracestring = "00-#{trace_id}-#{span_id}-#{trace_flags}"
119
- [trace_id, span_id, trace_flags, tracestring]
114
+ if defined?(::OpenTelemetry::Trace)
115
+ span = ::OpenTelemetry::Trace.current_span
116
+ trace_id = span.context.hex_trace_id
117
+ span_id = span.context.hex_span_id
118
+ trace_flags = span.context.trace_flags.sampled? ? '01' : '00'
119
+ tracestring = "00-#{trace_id}-#{span_id}-#{trace_flags}"
120
+ [trace_id, span_id, trace_flags, tracestring]
121
+ else
122
+ %w[00000000000000000000000000000000 00000000 00 00-00000000000000000000000000000000-00000000-00]
123
+ end
120
124
  end
121
125
 
122
126
  # if true the trace info should be added to the log message
@@ -32,12 +32,9 @@ module SolarWindsAPM
32
32
  # === Returns:
33
33
  # * Boolean
34
34
  #
35
- def increment_metric(name, count = 1, with_hostname = false, tags_kvs = {}) # rubocop:disable Style/OptionalBooleanParameter
36
- return true unless SolarWindsAPM.loaded
37
-
38
- with_hostname = with_hostname ? 1 : 0
39
- tags, tags_count = make_tags(tags_kvs)
40
- SolarWindsAPM::CustomMetrics.increment(name.to_s, count, with_hostname, nil, tags, tags_count).zero?
35
+ def increment_metric(_name, _count = 1, _with_hostname = false, _tags_kvs = {}) # rubocop:disable Style/OptionalBooleanParameter
36
+ SolarWindsAPM.logger.warn { 'increment_metric have been deprecated. Please use opentelemetry metrics-sdk to log metrics data.' }
37
+ false
41
38
  end
42
39
 
43
40
  # Send values with counts
@@ -66,29 +63,15 @@ module SolarWindsAPM
66
63
  # === Returns:
67
64
  # * Boolean
68
65
  #
69
- def summary_metric(name, value, count = 1, with_hostname = false, tags_kvs = {}) # rubocop:disable Style/OptionalBooleanParameter
70
- return true unless SolarWindsAPM.loaded
71
-
72
- with_hostname = with_hostname ? 1 : 0
73
- tags, tags_count = make_tags(tags_kvs)
74
- SolarWindsAPM::CustomMetrics.summary(name.to_s, value, count, with_hostname, nil, tags, tags_count).zero?
66
+ def summary_metric(_name, _value, _count = 1, _with_hostname = false, _tags_kvs = {}) # rubocop:disable Style/OptionalBooleanParameter
67
+ SolarWindsAPM.logger.warn { 'summary_metric have been deprecated. Please use opentelemetry metrics-sdk to log metrics data.' }
68
+ false
75
69
  end
76
70
 
77
71
  private
78
72
 
79
- def make_tags(tags_kvs)
80
- unless tags_kvs.is_a?(Hash)
81
- SolarWindsAPM.logger.warn("[solarwinds_apm/metrics] CustomMetrics received tags_kvs that are not a Hash (found #{tags_kvs.class}), setting tags_kvs = {}")
82
- tags_kvs = {}
83
- end
84
- count = tags_kvs.size
85
- tags = SolarWindsAPM::MetricTags.new(count)
86
-
87
- tags_kvs.each_with_index do |(k, v), i|
88
- tags.add(i, k.to_s, v.to_s)
89
- end
90
-
91
- [tags, count]
73
+ def make_tags(_tags_kvs)
74
+ nil
92
75
  end
93
76
  end
94
77
  end
@@ -12,22 +12,12 @@ module SolarWindsAPM
12
12
  # Wait for SolarWinds to be ready to send traces.
13
13
  #
14
14
  # This may be useful in short lived background processes when it is important to capture
15
- # information during the whole time the process is running. It returns boolean if <tt>integer_response</tt> is false,
16
- # and it will return integer if setting <tt>integer_response</tt> as true.
15
+ # information during the whole time the process is running.
17
16
  # Usually SolarWinds doesn't block an application while it is starting up.
18
17
  #
19
- # For status code reference:
20
- # 0: unknown error
21
- # 1: is ready
22
- # 2: not ready yet, try later
23
- # 3: limit exceeded
24
- # 4: invalid API key
25
- # 5: connection error
26
- #
27
18
  # === Argument:
28
19
  #
29
20
  # * +wait_milliseconds+ - (int, default 3000) the maximum time to wait in milliseconds
30
- # * +integer_response+ - (boolean, default false) determine whether return status code of reporter or not
31
21
  #
32
22
  # === Example:
33
23
  #
@@ -35,24 +25,19 @@ module SolarWindsAPM
35
25
  # Logger.info "SolarWindsAPM not ready after 10 seconds, no metrics will be sent"
36
26
  # end
37
27
  #
38
- # # with status code print out
39
- # status = SolarWindsAPM::API.solarwinds_ready?(10_000, integer_response: true)
40
- # unless status == 1
41
- # Logger.info "SolarWindsAPM not ready after 10 seconds, no metrics will be sent. Error code "#{status}"
42
- # end
43
- #
44
28
  # === Returns:
45
- # * Boolean (if integer_response: false)
46
- # * Integer (if integer_response: true)
47
- #
48
- def solarwinds_ready?(wait_milliseconds = 3000, integer_response: false)
49
- return false unless SolarWindsAPM.loaded
50
-
51
- is_ready = SolarWindsAPM::Context.isReady(wait_milliseconds)
52
-
53
- return is_ready if integer_response
29
+ # * Boolean
30
+ #
31
+ def solarwinds_ready?(wait_milliseconds = 3000, integer_response: nil)
32
+ unless integer_response.nil?
33
+ SolarWindsAPM.logger.warn do
34
+ 'Deprecation: solarwinds_ready? no longer accepts integer_response, this parameter will be removed in the next release.'
35
+ end
36
+ end
54
37
 
55
- is_ready == 1
38
+ root_sampler = ::OpenTelemetry.tracer_provider.sampler.instance_variable_get(:@root)
39
+ is_ready = root_sampler.wait_until_ready(wait_milliseconds / 1000)
40
+ !!is_ready
56
41
  end
57
42
  end
58
43
  end
@@ -39,23 +39,19 @@ module SolarWindsAPM
39
39
  #
40
40
  def set_transaction_name(custom_name = nil)
41
41
  status = true
42
- if ENV.fetch('SW_APM_ENABLED', 'true') == 'false' ||
43
- SolarWindsAPM::Context.toString == '99-00000000000000000000000000000000-0000000000000000-00'
44
- # library disabled or noop, just log and skip work.
45
- # TODO: can we have a single indicator that the API is in noop mode?
42
+ if ENV.fetch('SW_APM_ENABLED', 'true') == 'false'
46
43
  SolarWindsAPM.logger.debug { "[#{name}/#{__method__}] SolarWindsAPM is in disabled or noop mode." }
47
- elsif custom_name.nil? || custom_name.empty?
44
+ elsif SolarWindsAPM::OTelNativeConfig[:metrics_processor].nil?
48
45
  SolarWindsAPM.logger.warn do
49
- "[#{name}/#{__method__}] Set transaction name failed: custom_name is either nil or empty string."
46
+ "[#{name}/#{__method__}] Set transaction name failed: Solarwinds processor is missing. Noop mode."
50
47
  end
51
- status = false
52
- elsif SolarWindsAPM::OTelConfig[:metrics_processor].nil?
48
+ elsif custom_name.nil? || custom_name.empty?
53
49
  SolarWindsAPM.logger.warn do
54
- "[#{name}/#{__method__}] Set transaction name failed: Solarwinds processor is missing."
50
+ "[#{name}/#{__method__}] Set transaction name failed: custom_name is either nil or empty string."
55
51
  end
56
52
  status = false
57
53
  else
58
- solarwinds_processor = SolarWindsAPM::OTelConfig[:metrics_processor]
54
+ solarwinds_processor = SolarWindsAPM::OTelNativeConfig[:metrics_processor]
59
55
  current_span = ::OpenTelemetry::Trace.current_span
60
56
 
61
57
  if current_span.context.valid?
@@ -212,7 +212,7 @@ module SolarWindsAPM
212
212
 
213
213
  # Assure value is an integer
214
214
  @@config[key.to_sym] = new_value.to_i
215
- SolarWindsAPM.sample_rate(new_value) if SolarWindsAPM.loaded
215
+ SolarWindsAPM.sample_rate(new_value)
216
216
 
217
217
  when :transaction_settings
218
218
  compile_settings(value)
@@ -226,6 +226,12 @@ module SolarWindsAPM
226
226
  when :tag_sql
227
227
  enable_disable_config('SW_APM_TAG_SQL', key, value, false, bool: true)
228
228
 
229
+ when :http_proxy
230
+ SolarWindsAPM.logger.warn { ':http_proxy is deprecated' }
231
+
232
+ when :hostname_alias
233
+ SolarWindsAPM.logger.warn { ':hostname_alias is deprecated' }
234
+
229
235
  else
230
236
  @@config[key.to_sym] = value
231
237
 
@@ -32,5 +32,6 @@ module SolarWindsAPM
32
32
  INTL_SWO_OTEL_STATUS = 'otel.status_code'
33
33
  INTL_SWO_OTEL_STATUS_DESCRIPTION = 'otel.status_description'
34
34
  INTERNAL_TRIGGERED_TRACE = 'TriggeredTrace'
35
+ MAX_TXN_NAME_LENGTH = 256
35
36
  end
36
37
  end
@@ -19,8 +19,9 @@ module NoopAPI
19
19
  # Tracing
20
20
  module Tracing
21
21
  # (wait_milliseconds=3000, integer_response: false)
22
- def solarwinds_ready?(*_args, **options)
23
- options && options[:integer_response] ? 0 : false
22
+ def solarwinds_ready?(_wait_milliseconds = 3000, integer_response: false)
23
+ _noop = integer_response
24
+ false
24
25
  end
25
26
  end
26
27
 
@@ -55,10 +56,12 @@ module NoopAPI
55
56
  # CustomMetrics
56
57
  module CustomMetrics
57
58
  def increment_metric(*)
59
+ SolarWindsAPM.logger.warn { 'increment_metric have been deprecated. Please use opentelemetry metrics-sdk to log metrics data.' }
58
60
  false
59
61
  end
60
62
 
61
63
  def summary_metric(*)
64
+ SolarWindsAPM.logger.warn { 'summary_metric have been deprecated. Please use opentelemetry metrics-sdk to log metrics data.' }
62
65
  false
63
66
  end
64
67
  end
@@ -6,28 +6,4 @@
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 'noop/context'
10
- require_relative 'noop/metadata'
11
- require_relative 'noop/span'
12
9
  require_relative 'noop/api'
13
-
14
- module SolarWindsAPM
15
- include Oboe_metal
16
- # Reporter noop
17
- class Reporter
18
- ##
19
- # noop version of :send_report
20
- #
21
- def self.send_report(event, with_system_timestamp: false); end
22
-
23
- ##
24
- # noop version of :send_status
25
- #
26
- def self.send_status(event, context = nil, with_system_timestamp: false); end
27
-
28
- ##
29
- # noop version of :start
30
- #
31
- def self.start; end
32
- end
33
- end
@@ -8,66 +8,100 @@
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 = init_response_time_metrics
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
+ return if non_entry_span(span: span)
49
+
50
+ @transaction_name = calculate_transaction_names(span)
51
+ span.set_attribute(SW_TRANSACTION_NAME, @transaction_name)
52
+ @txn_manager.delete_root_context_h(span.context.hex_trace_id)
53
+ end
54
+
55
+ # @param [Span] span the (immutable) {Span} that just ended.
36
56
  def on_finish(span)
37
57
  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
58
  return if non_entry_span(span: span)
41
59
 
42
60
  record_request_metrics(span)
43
- record_sampling_metrics
44
61
 
45
- ::OpenTelemetry.meter_provider.metric_readers.each(&:pull)
62
+ # pull should work on any instrument from oboe_sampler
63
+ ::OpenTelemetry.meter_provider.metric_readers.each do |reader|
64
+ reader.pull if reader.respond_to? :pull
65
+ end
66
+
46
67
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] processor on_finish succeed" }
47
68
  rescue StandardError => e
48
69
  SolarWindsAPM.logger.info { "[#{self.class}/#{__method__}] error processing span on_finish: #{e.message}" }
49
70
  end
50
71
 
51
- private
72
+ # @param [optional Numeric] timeout An optional timeout in seconds.
73
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
74
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
75
+ def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
76
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
77
+ end
52
78
 
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
- }
79
+ # @param [optional Numeric] timeout An optional timeout in seconds.
80
+ # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if
81
+ # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred.
82
+ def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument
83
+ ::OpenTelemetry::SDK::Trace::Export::SUCCESS
59
84
  end
60
85
 
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
86
+ private
87
+
88
+ def init_response_time_metrics
89
+ # add the ExponentialBucketHistogram view
90
+ if defined? ::OpenTelemetry::Exporter::OTLP::Metrics && Gem::Version.new(::OpenTelemetry::Exporter::OTLP::Metrics::VERSION) >= Gem::Version.new('0.5.0')
91
+ ::OpenTelemetry.meter_provider.add_view('trace.service.response_time',
92
+ aggregation: ::OpenTelemetry::SDK::Metrics::Aggregation::ExponentialBucketHistogram.new(max_scale: 20),
93
+ type: :histogram,
94
+ unit: 'ms')
95
+ end
96
+
97
+ instrument = @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.')
98
+ { response_time: instrument }
65
99
  end
66
100
 
67
101
  def meter_attributes(span)
68
102
  meter_attrs = {
69
- 'sw.is_error' => error?(span) == 1,
70
- 'sw.transaction' => calculate_lambda_transaction_name(span)
103
+ SW_IS_ERROR => error?(span) == 1,
104
+ SW_TRANSACTION_NAME => @transaction_name
71
105
  }
72
106
 
73
107
  if span_http?(span)
@@ -76,26 +110,33 @@ module SolarWindsAPM
76
110
  meter_attrs['http.method'] = span.attributes[HTTP_METHOD] if span.attributes[HTTP_METHOD]
77
111
  end
78
112
  SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] meter_attrs: #{meter_attrs.inspect}" }
113
+ meter_attrs.compact!
79
114
  meter_attrs
80
115
  end
81
116
 
82
- def calculate_lambda_transaction_name(span)
83
- (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span.name || 'unknown').slice(0, 255)
117
+ def calculate_lambda_transaction_name(span_name)
118
+ (ENV['SW_APM_TRANSACTION_NAME'] || ENV['AWS_LAMBDA_FUNCTION_NAME'] || span_name || 'unknown').slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
84
119
  end
85
120
 
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
121
+ # Get trans_name and url_tran of this span instance.
122
+ # Predecessor order: custom SDK > env var SW_APM_TRANSACTION_NAME > automatic naming
123
+ def calculate_transaction_names(span)
124
+ return calculate_lambda_transaction_name(span.name) if SolarWindsAPM::Utils.determine_lambda
125
+
126
+ trace_span_id = "#{span.context.hex_trace_id}-#{span.context.hex_span_id}"
127
+ trans_name = @txn_manager.get(trace_span_id)
128
+ if trans_name
129
+ SolarWindsAPM.logger.debug do
130
+ "[#{self.class}/#{__method__}] found trans name from txn_manager: #{trans_name} by #{trace_span_id}"
131
+ end
132
+ @txn_manager.del(trace_span_id)
133
+ elsif !ENV['SW_APM_TRANSACTION_NAME'].to_s.empty?
134
+ trans_name = ENV.fetch('SW_APM_TRANSACTION_NAME', nil)
135
+ else
136
+ trans_name = span.attributes[HTTP_ROUTE] || nil
137
+ trans_name = span.name if trans_name.to_s.empty? && span.name
138
+ end
139
+ trans_name.to_s.slice(0, SolarWindsAPM::Constants::MAX_TXN_NAME_LENGTH)
99
140
  end
100
141
 
101
142
  def record_request_metrics(span)
@@ -106,31 +147,40 @@ module SolarWindsAPM
106
147
  @metrics[:response_time].record(span_time, attributes: meter_attrs)
107
148
  end
108
149
 
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)
150
+ # Calculate span time in microseconds (us) using start and end time
151
+ # in nanoseconds (ns). OTel span start/end_time are optional.
152
+ def calculate_span_time(start_time: nil, end_time: nil)
153
+ return 0 if start_time.nil? || end_time.nil?
114
154
 
115
- _, sample_count = SolarWindsAPM.oboe_api.consumeSampleCount
116
- SolarWindsAPM.logger.debug { "[#{self.class}/#{__method__}] sample_count: #{sample_count}" }
117
- @metrics[:samplecount].add(sample_count)
155
+ ((end_time.to_i - start_time.to_i) / 1e3).round
156
+ end
118
157
 
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)
158
+ # Calculate if this span instance has_error
159
+ # return [Integer]
160
+ def error?(span)
161
+ span.status.code == ::OpenTelemetry::Trace::Status::ERROR ? 1 : 0
162
+ end
122
163
 
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)
164
+ # This span from inbound HTTP request if from a SERVER by some http.method
165
+ def span_http?(span)
166
+ span.kind == ::OpenTelemetry::Trace::SpanKind::SERVER && !span.attributes[HTTP_METHOD].nil?
167
+ end
126
168
 
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)
169
+ # Calculate HTTP status_code from span or default to UNAVAILABLE
170
+ # Something went wrong in OTel or instrumented service crashed early
171
+ # if no status_code in attributes of HTTP span
172
+ def get_http_status_code(span)
173
+ span.attributes[HTTP_STATUS_CODE] || INVALID_HTTP_STATUS_CODE
174
+ end
130
175
 
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)
176
+ # check if it's entry span based on no parent or parent is remote
177
+ def non_entry_span(span: nil, parent_context: nil)
178
+ if parent_context
179
+ parent_span = ::OpenTelemetry::Trace.current_span(parent_context)
180
+ parent_span && parent_span.context != ::OpenTelemetry::Trace::SpanContext::INVALID && parent_span.context.remote? == false
181
+ elsif span
182
+ span.attributes['sw.is_entry_span'] != true
183
+ end
134
184
  end
135
185
  end
136
186
  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