solarwinds_apm 5.0.0

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +5 -0
  3. data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
  4. data/.github/workflows/build_and_release_gem.yml +112 -0
  5. data/.github/workflows/build_for_packagecloud.yml +70 -0
  6. data/.github/workflows/docker-images.yml +47 -0
  7. data/.github/workflows/run_cpluplus_tests.yml +73 -0
  8. data/.github/workflows/run_tests.yml +155 -0
  9. data/.github/workflows/scripts/test_install.rb +23 -0
  10. data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
  11. data/.github/workflows/test_on_4_linux.yml +161 -0
  12. data/.gitignore +39 -0
  13. data/.rubocop.yml +29 -0
  14. data/.yardopts +7 -0
  15. data/CHANGELOG.md +769 -0
  16. data/CONFIG.md +31 -0
  17. data/Gemfile +14 -0
  18. data/LICENSE +202 -0
  19. data/README.md +383 -0
  20. data/bin/solarwinds_apm_config +15 -0
  21. data/examples/prepend.rb +13 -0
  22. data/examples/sdk_examples.rb +158 -0
  23. data/ext/oboe_metal/README.md +69 -0
  24. data/ext/oboe_metal/extconf.rb +141 -0
  25. data/ext/oboe_metal/extconf_local.rb +75 -0
  26. data/ext/oboe_metal/lib/.keep +0 -0
  27. data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
  28. data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
  29. data/ext/oboe_metal/noop/noop.c +8 -0
  30. data/ext/oboe_metal/src/README.md +6 -0
  31. data/ext/oboe_metal/src/VERSION +2 -0
  32. data/ext/oboe_metal/src/bson/bson.h +220 -0
  33. data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
  34. data/ext/oboe_metal/src/frames.cc +247 -0
  35. data/ext/oboe_metal/src/frames.h +40 -0
  36. data/ext/oboe_metal/src/init_solarwinds_apm.cc +21 -0
  37. data/ext/oboe_metal/src/logging.cc +95 -0
  38. data/ext/oboe_metal/src/logging.h +35 -0
  39. data/ext/oboe_metal/src/oboe.h +1169 -0
  40. data/ext/oboe_metal/src/oboe_api.cpp +658 -0
  41. data/ext/oboe_metal/src/oboe_api.hpp +433 -0
  42. data/ext/oboe_metal/src/oboe_debug.h +59 -0
  43. data/ext/oboe_metal/src/oboe_swig_wrap.cc +7562 -0
  44. data/ext/oboe_metal/src/profiling.cc +435 -0
  45. data/ext/oboe_metal/src/profiling.h +78 -0
  46. data/ext/oboe_metal/test/CMakeLists.txt +53 -0
  47. data/ext/oboe_metal/test/FindGMock.cmake +43 -0
  48. data/ext/oboe_metal/test/README.md +56 -0
  49. data/ext/oboe_metal/test/frames_test.cc +164 -0
  50. data/ext/oboe_metal/test/profiling_test.cc +93 -0
  51. data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
  52. data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
  53. data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
  54. data/ext/oboe_metal/test/test.h +11 -0
  55. data/ext/oboe_metal/test/test_main.cc +32 -0
  56. data/init.rb +4 -0
  57. data/lib/oboe.rb +7 -0
  58. data/lib/oboe_metal.rb +172 -0
  59. data/lib/rails/generators/solarwinds_apm/install_generator.rb +47 -0
  60. data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +424 -0
  61. data/lib/solarwinds_apm/api/layerinit.rb +41 -0
  62. data/lib/solarwinds_apm/api/logging.rb +356 -0
  63. data/lib/solarwinds_apm/api/memcache.rb +37 -0
  64. data/lib/solarwinds_apm/api/metrics.rb +63 -0
  65. data/lib/solarwinds_apm/api/util.rb +98 -0
  66. data/lib/solarwinds_apm/api.rb +21 -0
  67. data/lib/solarwinds_apm/base.rb +160 -0
  68. data/lib/solarwinds_apm/config.rb +301 -0
  69. data/lib/solarwinds_apm/frameworks/grape.rb +96 -0
  70. data/lib/solarwinds_apm/frameworks/padrino.rb +78 -0
  71. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +100 -0
  72. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  73. data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  74. data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +88 -0
  75. data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +26 -0
  76. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  77. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +22 -0
  78. data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +103 -0
  79. data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +14 -0
  80. data/lib/solarwinds_apm/frameworks/rails.rb +100 -0
  81. data/lib/solarwinds_apm/frameworks/sinatra.rb +96 -0
  82. data/lib/solarwinds_apm/inst/bunny-client.rb +157 -0
  83. data/lib/solarwinds_apm/inst/bunny-consumer.rb +102 -0
  84. data/lib/solarwinds_apm/inst/curb.rb +288 -0
  85. data/lib/solarwinds_apm/inst/dalli.rb +89 -0
  86. data/lib/solarwinds_apm/inst/delayed_job.rb +100 -0
  87. data/lib/solarwinds_apm/inst/excon.rb +113 -0
  88. data/lib/solarwinds_apm/inst/faraday.rb +96 -0
  89. data/lib/solarwinds_apm/inst/graphql.rb +206 -0
  90. data/lib/solarwinds_apm/inst/grpc_client.rb +147 -0
  91. data/lib/solarwinds_apm/inst/grpc_server.rb +119 -0
  92. data/lib/solarwinds_apm/inst/httpclient.rb +181 -0
  93. data/lib/solarwinds_apm/inst/logger_formatter.rb +46 -0
  94. data/lib/solarwinds_apm/inst/logging_log_event.rb +24 -0
  95. data/lib/solarwinds_apm/inst/lumberjack_formatter.rb +9 -0
  96. data/lib/solarwinds_apm/inst/memcached.rb +86 -0
  97. data/lib/solarwinds_apm/inst/mongo.rb +246 -0
  98. data/lib/solarwinds_apm/inst/mongo2.rb +225 -0
  99. data/lib/solarwinds_apm/inst/moped.rb +466 -0
  100. data/lib/solarwinds_apm/inst/net_http.rb +60 -0
  101. data/lib/solarwinds_apm/inst/rack.rb +217 -0
  102. data/lib/solarwinds_apm/inst/rack_cache.rb +35 -0
  103. data/lib/solarwinds_apm/inst/redis.rb +273 -0
  104. data/lib/solarwinds_apm/inst/resque.rb +129 -0
  105. data/lib/solarwinds_apm/inst/rest-client.rb +43 -0
  106. data/lib/solarwinds_apm/inst/sequel.rb +241 -0
  107. data/lib/solarwinds_apm/inst/sidekiq-client.rb +63 -0
  108. data/lib/solarwinds_apm/inst/sidekiq-worker.rb +64 -0
  109. data/lib/solarwinds_apm/inst/typhoeus.rb +90 -0
  110. data/lib/solarwinds_apm/instrumentation.rb +22 -0
  111. data/lib/solarwinds_apm/loading.rb +65 -0
  112. data/lib/solarwinds_apm/logger.rb +14 -0
  113. data/lib/solarwinds_apm/noop/README.md +9 -0
  114. data/lib/solarwinds_apm/noop/context.rb +26 -0
  115. data/lib/solarwinds_apm/noop/metadata.rb +25 -0
  116. data/lib/solarwinds_apm/noop/profiling.rb +21 -0
  117. data/lib/solarwinds_apm/oboe_init_options.rb +191 -0
  118. data/lib/solarwinds_apm/ruby.rb +35 -0
  119. data/lib/solarwinds_apm/sdk/current_trace_info.rb +123 -0
  120. data/lib/solarwinds_apm/sdk/custom_metrics.rb +94 -0
  121. data/lib/solarwinds_apm/sdk/logging.rb +37 -0
  122. data/lib/solarwinds_apm/sdk/trace_context_headers.rb +69 -0
  123. data/lib/solarwinds_apm/sdk/tracing.rb +432 -0
  124. data/lib/solarwinds_apm/support/profiling.rb +22 -0
  125. data/lib/solarwinds_apm/support/trace_context.rb +53 -0
  126. data/lib/solarwinds_apm/support/trace_state.rb +69 -0
  127. data/lib/solarwinds_apm/support/trace_string.rb +89 -0
  128. data/lib/solarwinds_apm/support/transaction_metrics.rb +67 -0
  129. data/lib/solarwinds_apm/support/transaction_settings.rb +233 -0
  130. data/lib/solarwinds_apm/support/x_trace_options.rb +113 -0
  131. data/lib/solarwinds_apm/support.rb +12 -0
  132. data/lib/solarwinds_apm/support_report.rb +113 -0
  133. data/lib/solarwinds_apm/test.rb +165 -0
  134. data/lib/solarwinds_apm/thread_local.rb +26 -0
  135. data/lib/solarwinds_apm/util.rb +334 -0
  136. data/lib/solarwinds_apm/version.rb +17 -0
  137. data/lib/solarwinds_apm.rb +72 -0
  138. data/log/.keep +0 -0
  139. data/log/postgresql/.keep +0 -0
  140. data/solarwinds_apm.gemspec +52 -0
  141. data/yardoc_frontpage.md +24 -0
  142. metadata +228 -0
@@ -0,0 +1,432 @@
1
+ #--
2
+ # Copyright (c) 2016 SolarWinds, LLC.
3
+ # All rights reserved.
4
+ #++
5
+
6
+ module SolarWindsAPM
7
+ module SDK
8
+
9
+ ##
10
+ # Traces are best created with an <tt>SolarWindsAPM::SDK.start_trace</tt> block and
11
+ # <tt>SolarWindsAPM::SDK.trace</tt> blocks around calls to be traced.
12
+ # These two methods guarantee proper nesting of traces, handling of the tracing context, as well as avoiding
13
+ # broken traces in case of exceptions.
14
+ #
15
+ # Some optional keys that can be used in the +kvs+ hash:
16
+ # * +:Controller+
17
+ # * +:Action+
18
+ # * +:HTTP-Host+
19
+ # * +:URL+
20
+ # * +:Method+
21
+ #
22
+ # as well as custom keys. The information will show up in the raw data view of a span.
23
+ #
24
+ # Invalid keys: +:Label+, +:Layer+, +:Edge+, +:Timestamp+, +:Timestamp_u+, +:TransactionName+ (allowed in start_trace)
25
+ #
26
+ # The methods are exposed as singleton methods for SolarWindsAPM::SDK.
27
+ #
28
+ # === Usage:
29
+ # * +SolarWindsAPM::SDK.solarwinds_ready?+
30
+ # * +SolarWindsAPM::SDK.get_transaction_name+
31
+ # * +SolarWindsAPM::SDK.set_transaction_name+
32
+ # * +SolarWindsAPM::SDK.start_trace+
33
+ # * +SolarWindsAPM::SDK.start_trace_with_target+
34
+ # * +SolarWindsAPM::SDK.trace+
35
+ # * +SolarWindsAPM::SDK.trace_method+
36
+ # * +SolarWindsAPM::SDK.tracing?+
37
+ #
38
+ # === Example:
39
+ # class MonthlyCouponEmailJob
40
+ # def perform(*args)
41
+ #
42
+ # # KVs to report to the dashboard
43
+ # report_kvs = {}
44
+ # report_kvs[:Spec] = :job
45
+ # report_kvs[:Controller] = :MonthlyEmailJob
46
+ # report_kvs[:Action] = :CouponEmailer
47
+ #
48
+ # # Start tracing this job with start_trace
49
+ # SolarWindsAPM::SDK.start_trace('monthly_coupons', kvs: report_kvs) do
50
+ # monthly = MonthlyEmail.new(:CouponEmailer)
51
+ #
52
+ # # Trace a sub-component of this trace
53
+ # SolarWindsAPM::SDK.trace(self.class.name) do
54
+ #
55
+ # # The work to be done
56
+ # users = User.all
57
+ # users.each do |u|
58
+ # monthly.send(u.email)
59
+ # end
60
+ #
61
+ # end
62
+ # end
63
+ # end
64
+ # end
65
+ #
66
+ module Tracing
67
+
68
+ # Trace a given block of code.
69
+ #
70
+ # Also detects any exceptions thrown by the block and report errors.
71
+ #
72
+ # === Arguments:
73
+ # * +name+ - Name for the span to be used as label in the trace view.
74
+ # * +kvs:+ - (optional) A hash containing key/value pairs that will be reported along with the first event of this span.
75
+ # * +protect_op:+ - (optional) The operation being traced. Used to avoid double tracing operations that call each other.
76
+ #
77
+ # === Example:
78
+ #
79
+ # def computation_with_sw_apm(n)
80
+ # SolarWindsAPM::SDK.trace('computation', kvs: { :number => n }, protect_op: :computation) do
81
+ # return n if n == 0
82
+ # n + computation_with_sw_apm(n-1)
83
+ # end
84
+ # end
85
+ #
86
+ # result = computation_with_sw_apm(100)
87
+ #
88
+ # === Returns:
89
+ # * The result of the block.
90
+ #
91
+ def trace(name, kvs: {}, protect_op: nil)
92
+ return yield if !SolarWindsAPM.loaded || !SolarWindsAPM.tracing? || SolarWindsAPM.tracing_layer_op?(protect_op)
93
+
94
+ kvs.delete(:TransactionName)
95
+ kvs.delete('TransactionName')
96
+
97
+ SolarWindsAPM::API.log_entry(name, kvs, protect_op)
98
+ kvs[:Backtrace] && kvs.delete(:Backtrace) # to avoid sending backtrace twice (faster to check presence here)
99
+ begin
100
+ yield
101
+ rescue Exception => e
102
+ SolarWindsAPM::API.log_exception(name, e)
103
+ raise
104
+ ensure
105
+ SolarWindsAPM::API.log_exit(name, kvs, protect_op)
106
+ end
107
+ end
108
+
109
+ # Collect metrics and start tracing a given block of code.
110
+ #
111
+ # This will start a trace depending on configuration and probability, detect any exceptions
112
+ # thrown by the block, and report errors.
113
+ #
114
+ # This method is for request entry points where no trace has been started yet
115
+ # Nested calls to start_trace() will have the inner call override the outer call
116
+ # The behavior may be unexpected. After a trace is started with start_trace()
117
+ # trace() should be used to create spans within the started trace
118
+ #
119
+ # When start_trace returns control to the calling context, the trace will be
120
+ # completed and the tracing context will be cleared.
121
+ #
122
+ # === Arguments:
123
+ #
124
+ # * +name+ - Name for the span to be used as label in the trace view.
125
+ # * +kvs:+ - (optional) hash containing key/value pairs that will be reported with this span.
126
+ # The value of :TransactionName entry will set the transaction_name.
127
+ # * +headers:+ - hash containing incoming headers to extract w3c trace context
128
+ #
129
+ # === Example:
130
+ #
131
+ # def handle_request(request, response)
132
+ # # ... code that processes request and response ...
133
+ # end
134
+ #
135
+ # def handle_request_with_sw_apm(request, response)
136
+ # SolarWindsAPM::SDK.start_trace('custom_trace', kvs: { :TransactionName => 'handle_request' }) do
137
+ # handle_request(request, response)
138
+ # end
139
+ # end
140
+ #
141
+ # === Returns:
142
+ # * The result of the block.
143
+ #
144
+ def start_trace(name, kvs: {}, headers: {})
145
+ start_trace_with_target(name, target: {}, kvs: kvs, headers: headers) { yield }
146
+ end
147
+
148
+ # Collect metrics, trace a given block of code, and assign trace info to target.
149
+ #
150
+ # This will start a trace depending on configuration and probability, detect any exceptions
151
+ # thrown by the block, report errors, and assign an X-Trace to the target.
152
+ #
153
+ # The motivating use case for this is HTTP streaming in rails3. We need
154
+ # access to the exit event's trace id so we can set the header before any
155
+ # work is done, and before any headers are sent back to the client.
156
+ #
157
+ # === Arguments:
158
+ # * +name+ - Name for the span to be used as label in the trace view.
159
+ # * +target:+ - (optional) has to respond to #[]=, The target object in which to place the trace information.
160
+ # * +kvs:+ - (optional) Hash containing key/value pairs that will be reported with this span.
161
+ # * +headers:+ - (optional) Hash containing incoming headers to extract w3c trace context
162
+ #
163
+ # === Example:
164
+ #
165
+ # def handle_request(request, response)
166
+ # # ... code that processes request and response ...
167
+ # end
168
+ #
169
+ # def handle_request_with_sw_apm(request, response)
170
+ # SolarWindsAPM::SDK.start_trace_with_target('rails', headers: request.headers, target: response) do
171
+ # handle_request(request, response)
172
+ # end
173
+ # end
174
+ #
175
+ # === Returns:
176
+ # * The result of the block.
177
+ #
178
+ def start_trace_with_target(name, target: {}, kvs: {}, headers: {})
179
+ return yield unless SolarWindsAPM.loaded
180
+
181
+ SolarWindsAPM.transaction_name = kvs.delete('TransactionName') || kvs.delete(:TransactionName)
182
+
183
+ SolarWindsAPM::API.log_start(name, kvs, headers)
184
+ kvs[:Backtrace] && kvs.delete(:Backtrace) # to avoid sending backtrace twice (faster to check presence here)
185
+
186
+ # SolarWindsAPM::Event.startTrace creates an Event without an Edge
187
+ exit_evt = SolarWindsAPM::Event.startTrace(SolarWindsAPM::Context.get)
188
+
189
+ result = begin
190
+ SolarWindsAPM::API.send_metrics(name, kvs) do
191
+ target['X-Trace'] = SolarWindsAPM::EventUtil.metadataString(exit_evt)
192
+ yield
193
+ end
194
+ rescue Exception => e
195
+ SolarWindsAPM::API.log_exception(name, e)
196
+ exit_evt.addEdge(SolarWindsAPM::Context.get)
197
+ trace_parent = SolarWindsAPM::API.log_end(name, kvs, exit_evt)
198
+ e.instance_variable_set(:@tracestring, trace_parent)
199
+ raise
200
+ end
201
+
202
+ exit_evt.addEdge(SolarWindsAPM::Context.get)
203
+ SolarWindsAPM::API.log_end(name, kvs, exit_evt)
204
+
205
+ result
206
+ end
207
+
208
+ ##
209
+ # Add tracing to a given method
210
+ #
211
+ # This instruments the given method so that every time it is called it
212
+ # will create a span depending on the current context.
213
+ #
214
+ # The method can be of any (accessible) type (instance, singleton,
215
+ # private, protected etc.).
216
+ #
217
+ # The motivating use case for this is MetalController methods in Rails,
218
+ # which can't be auto-instrumented.
219
+ #
220
+ # === Arguments:
221
+ # * +klass+ - The module/class the method belongs to.
222
+ # * +method+ - The method name as symbol
223
+ # * +config:+ - (optional) possible keys are:
224
+ # :name the name of the span (default: the method name)
225
+ # :backtrace true/false (default: false) if true the backtrace will be added to the space
226
+ # * +kvs:+ - (optional) hash containing key/value pairs that will be reported with this span.
227
+ #
228
+ # === Example:
229
+ #
230
+ # module ExampleModule
231
+ # def do_sum(a, b)
232
+ # a + b
233
+ # end
234
+ # end
235
+ #
236
+ # SolarWindsAPM::SDK.trace_method(ExampleModule,
237
+ # :do_sum,
238
+ # config: {name: 'computation', backtrace: true},
239
+ # kvs: { CustomKey: "some_info"})
240
+ #
241
+ def trace_method(klass, method, config: {}, kvs: {})
242
+ # If we're on an unsupported platform (ahem Mac), just act
243
+ # like we did something to nicely play the no-op part.
244
+ return true unless SolarWindsAPM.loaded
245
+
246
+ if !klass.is_a?(Module)
247
+ SolarWindsAPM.logger.warn "[solarwinds_apm/error] trace_method: Not sure what to do with #{klass}. Send a class or module."
248
+ return false
249
+ end
250
+
251
+ if method.is_a?(String)
252
+ method = method.to_sym
253
+ elsif !method.is_a?(Symbol)
254
+ SolarWindsAPM.logger.warn "[solarwinds_apm/error] trace_method: Not sure what to do with #{method}. Send a string or symbol for method."
255
+ return false
256
+ end
257
+
258
+ instance_method = klass.instance_methods.include?(method) || klass.private_instance_methods.include?(method)
259
+ class_method = klass.singleton_methods.include?(method)
260
+
261
+ # Make sure the request klass::method exists
262
+ if !instance_method && !class_method
263
+ SolarWindsAPM.logger.warn "[solarwinds_apm/error] trace_method: Can't instrument #{klass}.#{method} as it doesn't seem to exist."
264
+ SolarWindsAPM.logger.warn "[solarwinds_apm/error] #{__FILE__}:#{__LINE__}"
265
+ return false
266
+ end
267
+
268
+ # Strip '!' or '?' from method if present
269
+ safe_method_name = method.to_s.chop if method.to_s =~ /\?$|\!$/
270
+ safe_method_name ||= method
271
+
272
+ without_sw_apm = "#{safe_method_name}_without_sw_apm"
273
+ with_sw_apm = "#{safe_method_name}_with_sw_apm"
274
+
275
+ # Check if already profiled
276
+ if instance_method && klass.instance_methods.include?(with_sw_apm.to_sym) ||
277
+ class_method && klass.singleton_methods.include?(with_sw_apm.to_sym)
278
+ SolarWindsAPM.logger.warn "[solarwinds_apm/error] trace_method: #{klass}::#{method} already instrumented.\n#{__FILE__}:#{__LINE__}"
279
+ return false
280
+ end
281
+
282
+ report_kvs = kvs.dup
283
+ if defined?(::AbstractController::Base) && klass.ancestors.include?(::AbstractController::Base)
284
+ report_kvs[:Controller] = klass.to_s
285
+ report_kvs[:Action] = method.to_s
286
+ else
287
+ klass.is_a?(Class) ? report_kvs[:Class] = klass.to_s : report_kvs[:Module] = klass.to_s
288
+ report_kvs[:MethodName] = safe_method_name
289
+ end
290
+ backtrace = config[:backtrace]
291
+
292
+ name = config[:name] || method
293
+ if instance_method
294
+ klass.class_eval do
295
+ define_method(with_sw_apm) do |*args, &block|
296
+ # if this is a rails controller we want to set the transaction for the outbound metrics
297
+ if report_kvs[:Controller] && defined?(request) && defined?(request.env)
298
+ request.env['solarwinds_apm.controller'] = report_kvs[:Controller]
299
+ request.env['solarwinds_apm.action'] = report_kvs[:Action]
300
+ end
301
+
302
+ SolarWindsAPM::SDK.trace(name, kvs: report_kvs) do
303
+ report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if backtrace
304
+ send(without_sw_apm, *args, &block)
305
+ end
306
+ end
307
+
308
+ alias_method without_sw_apm, method.to_s
309
+ alias_method method.to_s, with_sw_apm
310
+ end
311
+ elsif class_method
312
+ klass.define_singleton_method(with_sw_apm) do |*args, &block|
313
+ SolarWindsAPM::SDK.trace(name, kvs: report_kvs) do
314
+ report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if backtrace
315
+ send(without_sw_apm, *args, &block)
316
+ end
317
+ end
318
+
319
+ klass.singleton_class.class_eval do
320
+ alias_method without_sw_apm, method.to_s
321
+ alias_method method.to_s, with_sw_apm
322
+ end
323
+ end
324
+ true
325
+ end
326
+
327
+ ##
328
+ # Provide a custom transaction name
329
+ #
330
+ # The SolarWindsAPM gem tries to create meaningful transaction names from controller+action
331
+ # or something similar depending on the framework used. However, you may want to override the
332
+ # transaction name to better describe your instrumented operation.
333
+ #
334
+ # Take note that on the dashboard the transaction name is converted to lowercase, and might be
335
+ # truncated with invalid characters replaced. Method calls with an empty string or a non-string
336
+ # argument won't change the current transaction name.
337
+ #
338
+ # The configuration +SolarWindsAPM.Config+['transaction_name']+['prepend_domain']+ can be set to
339
+ # true to have the domain name prepended to the transaction name when an event or a metric are
340
+ # logged. This is a global setting.
341
+ #
342
+ # === Argument:
343
+ #
344
+ # * +name+ - A non-empty string with the custom transaction name
345
+ #
346
+ # === Example:
347
+ #
348
+ # class DogfoodsController < ApplicationController
349
+ #
350
+ # def create
351
+ # @dogfood = Dogfood.new(params.permit(:brand, :name))
352
+ # @dogfood.save
353
+ #
354
+ # SolarWindsAPM::SDK.set_transaction_name("dogfoodscontroller.create_for_#{params[:brand]}")
355
+ #
356
+ # redirect_to @dogfood
357
+ # end
358
+ #
359
+ # end
360
+ #
361
+ # === Returns:
362
+ # * (String or nil) the current transaction name
363
+ #
364
+ def set_transaction_name(name)
365
+ if name.is_a?(String) && name.strip != ''
366
+ SolarWindsAPM.transaction_name = name
367
+ else
368
+ SolarWindsAPM.logger.debug "[solarwinds_apm/api] Could not set transaction name, provided name is empty or not a String."
369
+ end
370
+ SolarWindsAPM.transaction_name
371
+ end
372
+
373
+ # Get the currently set custom transaction name.
374
+ #
375
+ # This is provided for testing
376
+ #
377
+ # === Returns:
378
+ # * (String or nil) the current transaction name (without domain prepended)
379
+ #
380
+ def get_transaction_name
381
+ SolarWindsAPM.transaction_name
382
+ end
383
+
384
+ # Determine if this transaction is being traced.
385
+ #
386
+ # Tracing puts some extra load on a system, therefore not all transaction are traced.
387
+ # The +tracing?+ method helps to determine this so that extra work can be avoided when not tracing.
388
+ #
389
+ # === Example:
390
+ #
391
+ # kvs = expensive_info_gathering_method if SolarWindsAPM::SDK.tracing?
392
+ # SolarWindsAPM::SDK.trace('some_span', kvs: kvs) do
393
+ # db_request
394
+ # end
395
+ #
396
+ def tracing?
397
+ SolarWindsAPM.tracing?
398
+ end
399
+
400
+ # Wait for SolarWinds to be ready to send traces.
401
+ #
402
+ # This may be useful in short lived background processes when it is important to capture
403
+ # information during the whole time the process is running. Usually SolarWinds doesn't block an
404
+ # application while it is starting up.
405
+ #
406
+ # === Argument:
407
+ #
408
+ # * +wait_milliseconds+ (int, default 3000) the maximum time to wait in milliseconds
409
+ #
410
+ # === Example:
411
+ #
412
+ # unless SolarWindsAPM::SDK.solarwinds_ready?(10_000)
413
+ # Logger.info "SolarWindsAPM not ready after 10 seconds, no metrics will be sent"
414
+ # end
415
+ #
416
+ def solarwinds_ready?(wait_milliseconds = 3000)
417
+ return false unless SolarWindsAPM.loaded
418
+ # These codes are returned by isReady:
419
+ # OBOE_SERVER_RESPONSE_UNKNOWN 0
420
+ # OBOE_SERVER_RESPONSE_OK 1
421
+ # OBOE_SERVER_RESPONSE_TRY_LATER 2
422
+ # OBOE_SERVER_RESPONSE_LIMIT_EXCEEDED 3
423
+ # OBOE_SERVER_RESPONSE_INVALID_API_KEY 4
424
+ # OBOE_SERVER_RESPONSE_CONNECT_ERROR 5
425
+ SolarWindsAPM::Context.isReady(wait_milliseconds) == 1
426
+ end
427
+ end
428
+
429
+ extend Tracing
430
+
431
+ end
432
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2020 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ class Profiling
6
+
7
+ def self.run
8
+ # TODO
9
+ # add back at some point but for now NH is not ready for profiling
10
+ SolarWindsAPM::Config.profiling = :disabled
11
+
12
+ # allow enabling and disabling and setting interval interactively
13
+ return yield unless SolarWindsAPM::Config.profiling == :enabled && SolarWindsAPM.tracing?
14
+
15
+ CProfiler.run(Thread.current, SolarWindsAPM::Config.profiling_interval) do
16
+ # for some reason `return` is needed here
17
+ # this is yielded by c-code, but why it needs `return` ... ????
18
+ return yield
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+
6
+ class TraceContext
7
+
8
+ attr_reader :traceparent, :tracestate, :tracestring, :sw_member_value
9
+
10
+ def initialize(headers = {})
11
+ return if headers.nil? || headers.empty?
12
+
13
+ # we won't propagate this context if the traceparent is invalid
14
+ traceparent, tracestate = ingest(headers)
15
+ return unless traceparent.is_a?(String) && SolarWindsAPM::TraceString.valid?(traceparent)
16
+
17
+ @traceparent = traceparent
18
+ @tracestate = tracestate
19
+
20
+ if @tracestate
21
+ @sw_member_value = TraceState.sw_member_value(@tracestate)
22
+ @tracestring = SolarWindsAPM::TraceString.replace_span_id_flags(@traceparent, @sw_member_value)
23
+ end
24
+
25
+ @tracestring ||= @traceparent
26
+ end
27
+
28
+ # these are event kvs, not headers
29
+ # called by log_start, lets reset to nil if there is no info
30
+ def add_traceinfo(kvs = {})
31
+ kvs['sw.tracestate_parent_id'] = @sw_member_value ? @sw_member_value[0...-3] : nil
32
+ kvs['sw.w3c.tracestate'] = @tracestate ? @tracestate : nil
33
+ kvs
34
+ end
35
+
36
+ private
37
+
38
+ def ingest(headers)
39
+ traceparent_key = headers.keys.find do |key|
40
+ key.to_s.downcase =~ /^(http){0,1}[_-]{0,1}traceparent$/
41
+ end
42
+
43
+ tracestate_key = headers.keys.find do |key|
44
+ key.to_s.downcase =~ /^(http){0,1}[_-]{0,1}tracestate$/
45
+ end
46
+
47
+ return nil, nil unless traceparent_key && tracestate_key
48
+
49
+ [headers[traceparent_key], headers[tracestate_key]]
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,69 @@
1
+ # Copyright (c) 2021 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+
6
+ # test coverage through instrumentation_mocked and inst tests
7
+ module TraceState
8
+ class << self
9
+
10
+ # prepends our kv to tracestate string
11
+ # value has to be in W3C format
12
+ def add_sw_member(tracestate, value)
13
+ return tracestate unless sw_value_valid?(value)
14
+
15
+ result = "#{SW_APM_TRACESTATE_ID}=#{value}#{remove_sw(tracestate)}"
16
+
17
+ if result.bytesize > SW_APM_MAX_TRACESTATE_BYTES
18
+ return reduce_size(result)
19
+ end
20
+
21
+ result
22
+ end
23
+
24
+ # extract the 'sw' tracestate member, parent_id/edge, and flags
25
+ def sw_member_value(tracestate)
26
+ regex = /^.*(#{SW_APM_TRACESTATE_ID}=(?<sw_member_value>[a-f0-9]{16}-[a-f0-9]{2})).*$/.freeze
27
+
28
+ matches = regex.match(tracestate)
29
+
30
+ return nil unless matches
31
+
32
+ matches[:sw_member_value]
33
+ end
34
+
35
+ private
36
+
37
+ # returns tracestate with leading comma for specific use
38
+ # in add_sw_member
39
+ def remove_sw(tracestate)
40
+ return "" unless tracestate
41
+ tracestate.gsub!(/,{0,1}\s*#{SW_APM_TRACESTATE_ID}=[^,]*/, '')
42
+ (tracestate.size > 0 && tracestate[0] != ',') ? ",#{tracestate}" : tracestate
43
+ end
44
+
45
+ # this validates the format of the value of our vendor entry
46
+ def sw_value_valid?(value)
47
+ value =~ /^[a-f0-9]{16}-0[01]$/.freeze
48
+ end
49
+
50
+ def reduce_size(tracestate)
51
+ size = tracestate.bytesize
52
+ members = tracestate.split(',').reverse
53
+
54
+ large_members = members.select { |m| m.bytesize > SW_APM_MAX_TRACESTATE_MEMBER_BYTES }
55
+ while large_members[0] && size > SW_APM_MAX_TRACESTATE_BYTES
56
+ size -= large_members[0].bytesize + 1 # add 1 for comma
57
+ members.delete(large_members.shift)
58
+ end
59
+
60
+ tracestate = members.reverse.join(',')
61
+ until tracestate.bytesize <= SW_APM_MAX_TRACESTATE_BYTES do
62
+ tracestate.gsub!(/,[^,]*$/, '')
63
+ end
64
+ tracestate
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,89 @@
1
+ # Copyright (c) SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ module TraceString
6
+ # This module processes and queries strings of the format defined in
7
+ # https://www.w3.org/TR/trace-context/#traceparent-header
8
+
9
+ # Regexp copied from Ruby OT trace_string.rb
10
+ REGEXP = /^(?<tracestring>(?<version>[a-f0-9]{2})-(?<trace_id>[a-f0-9]{32})-(?<span_id>[a-f0-9]{16})-(?<flags>[a-f0-9]{2}))$/.freeze
11
+ private_constant :REGEXP
12
+
13
+ class << self
14
+
15
+ def split(tracestring)
16
+ matches = REGEXP.match(tracestring)
17
+
18
+ matches
19
+ end
20
+
21
+ # un-initialized (all 0 trace-id) tracestrings are not valid
22
+ def valid?(tracestring)
23
+ matches = REGEXP.match(tracestring)
24
+
25
+ matches && matches[:trace_id] != ("0" * 32)
26
+ end
27
+
28
+ def sampled?(tracestring)
29
+ matches = REGEXP.match(tracestring)
30
+
31
+ matches && matches[:flags][-1].to_i & 1 == 1
32
+ end
33
+
34
+ def trace_id(tracestring)
35
+ matches = REGEXP.match(tracestring)
36
+
37
+ matches && matches[:trace_id]
38
+ end
39
+
40
+ def span_id(tracestring)
41
+ matches = REGEXP.match(tracestring)
42
+
43
+ matches && matches[:span_id]
44
+ end
45
+
46
+ # Extract and return the span_id and flags
47
+ def span_id_flags(tracestring)
48
+ matches = REGEXP.match(tracestring)
49
+
50
+ matches && "#{matches[:span_id]}-#{matches[:flags]}"
51
+ end
52
+
53
+ def set_sampled(tracestring)
54
+ return unless REGEXP.match(tracestring)
55
+
56
+ last = tracestring[-2..-1].hex | 0x00000001
57
+ last = last.to_s(16).rjust(2, '0')
58
+
59
+ tracestring[-2..-1] = last
60
+ tracestring
61
+ end
62
+
63
+ def unset_sampled(tracestring)
64
+ return unless REGEXP.match(tracestring)
65
+
66
+ # shift left and right to set last bit to zero
67
+ last = tracestring[-2..-1].hex >> 1 << 1
68
+ last = last.to_s(16).rjust(2, '0')
69
+
70
+ tracestring[-2..-1] = last
71
+ tracestring
72
+ end
73
+
74
+ # !!! garbage in garbage out !!!
75
+ # span_id_flag are not checked for validity
76
+ # method is only used in TraceContext, where span_id_flags arg
77
+ # is created and is either valid or nil
78
+ def replace_span_id_flags(tracestring, span_id_flags)
79
+ return unless REGEXP.match(tracestring)
80
+ return tracestring unless span_id_flags =~ /^[a-f0-9]{16}-[a-f0-9]{2}$/
81
+
82
+ matches = REGEXP.match(tracestring)
83
+
84
+ "#{matches[:version]}-#{matches[:trace_id]}-#{span_id_flags}"
85
+ end
86
+
87
+ end
88
+ end
89
+ end