solarwinds_apm 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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