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.
- checksums.yaml +7 -0
- data/.dockerignore +5 -0
- data/.github/ISSUE_TEMPLATE/bug-or-feature-request.md +16 -0
- data/.github/workflows/build_and_release_gem.yml +112 -0
- data/.github/workflows/build_for_packagecloud.yml +70 -0
- data/.github/workflows/docker-images.yml +47 -0
- data/.github/workflows/run_cpluplus_tests.yml +73 -0
- data/.github/workflows/run_tests.yml +155 -0
- data/.github/workflows/scripts/test_install.rb +23 -0
- data/.github/workflows/swig/swig-v4.0.2.tar.gz +0 -0
- data/.github/workflows/test_on_4_linux.yml +161 -0
- data/.gitignore +39 -0
- data/.rubocop.yml +29 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +31 -0
- data/Gemfile +14 -0
- data/LICENSE +202 -0
- data/README.md +383 -0
- data/bin/solarwinds_apm_config +15 -0
- data/examples/prepend.rb +13 -0
- data/examples/sdk_examples.rb +158 -0
- data/ext/oboe_metal/README.md +69 -0
- data/ext/oboe_metal/extconf.rb +141 -0
- data/ext/oboe_metal/extconf_local.rb +75 -0
- data/ext/oboe_metal/lib/.keep +0 -0
- data/ext/oboe_metal/lib/liboboe-1.0-alpine-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/lib/liboboe-1.0-x86_64.so.0.0.0.sha256 +1 -0
- data/ext/oboe_metal/noop/noop.c +8 -0
- data/ext/oboe_metal/src/README.md +6 -0
- data/ext/oboe_metal/src/VERSION +2 -0
- data/ext/oboe_metal/src/bson/bson.h +220 -0
- data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
- data/ext/oboe_metal/src/frames.cc +247 -0
- data/ext/oboe_metal/src/frames.h +40 -0
- data/ext/oboe_metal/src/init_solarwinds_apm.cc +21 -0
- data/ext/oboe_metal/src/logging.cc +95 -0
- data/ext/oboe_metal/src/logging.h +35 -0
- data/ext/oboe_metal/src/oboe.h +1169 -0
- data/ext/oboe_metal/src/oboe_api.cpp +658 -0
- data/ext/oboe_metal/src/oboe_api.hpp +433 -0
- data/ext/oboe_metal/src/oboe_debug.h +59 -0
- data/ext/oboe_metal/src/oboe_swig_wrap.cc +7562 -0
- data/ext/oboe_metal/src/profiling.cc +435 -0
- data/ext/oboe_metal/src/profiling.h +78 -0
- data/ext/oboe_metal/test/CMakeLists.txt +53 -0
- data/ext/oboe_metal/test/FindGMock.cmake +43 -0
- data/ext/oboe_metal/test/README.md +56 -0
- data/ext/oboe_metal/test/frames_test.cc +164 -0
- data/ext/oboe_metal/test/profiling_test.cc +93 -0
- data/ext/oboe_metal/test/ruby_inc_dir.rb +8 -0
- data/ext/oboe_metal/test/ruby_prefix.rb +8 -0
- data/ext/oboe_metal/test/ruby_test_helper.rb +67 -0
- data/ext/oboe_metal/test/test.h +11 -0
- data/ext/oboe_metal/test/test_main.cc +32 -0
- data/init.rb +4 -0
- data/lib/oboe.rb +7 -0
- data/lib/oboe_metal.rb +172 -0
- data/lib/rails/generators/solarwinds_apm/install_generator.rb +47 -0
- data/lib/rails/generators/solarwinds_apm/templates/solarwinds_apm_initializer.rb +424 -0
- data/lib/solarwinds_apm/api/layerinit.rb +41 -0
- data/lib/solarwinds_apm/api/logging.rb +356 -0
- data/lib/solarwinds_apm/api/memcache.rb +37 -0
- data/lib/solarwinds_apm/api/metrics.rb +63 -0
- data/lib/solarwinds_apm/api/util.rb +98 -0
- data/lib/solarwinds_apm/api.rb +21 -0
- data/lib/solarwinds_apm/base.rb +160 -0
- data/lib/solarwinds_apm/config.rb +301 -0
- data/lib/solarwinds_apm/frameworks/grape.rb +96 -0
- data/lib/solarwinds_apm/frameworks/padrino.rb +78 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller.rb +100 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/action_view.rb +88 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/active_record.rb +26 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +22 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +103 -0
- data/lib/solarwinds_apm/frameworks/rails/inst/logger_formatters.rb +14 -0
- data/lib/solarwinds_apm/frameworks/rails.rb +100 -0
- data/lib/solarwinds_apm/frameworks/sinatra.rb +96 -0
- data/lib/solarwinds_apm/inst/bunny-client.rb +157 -0
- data/lib/solarwinds_apm/inst/bunny-consumer.rb +102 -0
- data/lib/solarwinds_apm/inst/curb.rb +288 -0
- data/lib/solarwinds_apm/inst/dalli.rb +89 -0
- data/lib/solarwinds_apm/inst/delayed_job.rb +100 -0
- data/lib/solarwinds_apm/inst/excon.rb +113 -0
- data/lib/solarwinds_apm/inst/faraday.rb +96 -0
- data/lib/solarwinds_apm/inst/graphql.rb +206 -0
- data/lib/solarwinds_apm/inst/grpc_client.rb +147 -0
- data/lib/solarwinds_apm/inst/grpc_server.rb +119 -0
- data/lib/solarwinds_apm/inst/httpclient.rb +181 -0
- data/lib/solarwinds_apm/inst/logger_formatter.rb +46 -0
- data/lib/solarwinds_apm/inst/logging_log_event.rb +24 -0
- data/lib/solarwinds_apm/inst/lumberjack_formatter.rb +9 -0
- data/lib/solarwinds_apm/inst/memcached.rb +86 -0
- data/lib/solarwinds_apm/inst/mongo.rb +246 -0
- data/lib/solarwinds_apm/inst/mongo2.rb +225 -0
- data/lib/solarwinds_apm/inst/moped.rb +466 -0
- data/lib/solarwinds_apm/inst/net_http.rb +60 -0
- data/lib/solarwinds_apm/inst/rack.rb +217 -0
- data/lib/solarwinds_apm/inst/rack_cache.rb +35 -0
- data/lib/solarwinds_apm/inst/redis.rb +273 -0
- data/lib/solarwinds_apm/inst/resque.rb +129 -0
- data/lib/solarwinds_apm/inst/rest-client.rb +43 -0
- data/lib/solarwinds_apm/inst/sequel.rb +241 -0
- data/lib/solarwinds_apm/inst/sidekiq-client.rb +63 -0
- data/lib/solarwinds_apm/inst/sidekiq-worker.rb +64 -0
- data/lib/solarwinds_apm/inst/typhoeus.rb +90 -0
- data/lib/solarwinds_apm/instrumentation.rb +22 -0
- data/lib/solarwinds_apm/loading.rb +65 -0
- data/lib/solarwinds_apm/logger.rb +14 -0
- data/lib/solarwinds_apm/noop/README.md +9 -0
- data/lib/solarwinds_apm/noop/context.rb +26 -0
- data/lib/solarwinds_apm/noop/metadata.rb +25 -0
- data/lib/solarwinds_apm/noop/profiling.rb +21 -0
- data/lib/solarwinds_apm/oboe_init_options.rb +191 -0
- data/lib/solarwinds_apm/ruby.rb +35 -0
- data/lib/solarwinds_apm/sdk/current_trace_info.rb +123 -0
- data/lib/solarwinds_apm/sdk/custom_metrics.rb +94 -0
- data/lib/solarwinds_apm/sdk/logging.rb +37 -0
- data/lib/solarwinds_apm/sdk/trace_context_headers.rb +69 -0
- data/lib/solarwinds_apm/sdk/tracing.rb +432 -0
- data/lib/solarwinds_apm/support/profiling.rb +22 -0
- data/lib/solarwinds_apm/support/trace_context.rb +53 -0
- data/lib/solarwinds_apm/support/trace_state.rb +69 -0
- data/lib/solarwinds_apm/support/trace_string.rb +89 -0
- data/lib/solarwinds_apm/support/transaction_metrics.rb +67 -0
- data/lib/solarwinds_apm/support/transaction_settings.rb +233 -0
- data/lib/solarwinds_apm/support/x_trace_options.rb +113 -0
- data/lib/solarwinds_apm/support.rb +12 -0
- data/lib/solarwinds_apm/support_report.rb +113 -0
- data/lib/solarwinds_apm/test.rb +165 -0
- data/lib/solarwinds_apm/thread_local.rb +26 -0
- data/lib/solarwinds_apm/util.rb +334 -0
- data/lib/solarwinds_apm/version.rb +17 -0
- data/lib/solarwinds_apm.rb +72 -0
- data/log/.keep +0 -0
- data/log/postgresql/.keep +0 -0
- data/solarwinds_apm.gemspec +52 -0
- data/yardoc_frontpage.md +24 -0
- 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
|