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,217 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ require 'uri'
5
+ require 'cgi'
6
+
7
+ if SolarWindsAPM.loaded
8
+ module SolarWindsAPM
9
+ ##
10
+ # SolarWindsAPM::Rack
11
+ #
12
+ # The SolarWindsAPM::Rack middleware used to sample a subset of incoming
13
+ # requests for instrumentation and reporting. Tracing context can
14
+ # be received here (via the X-Trace HTTP header) or initiated here
15
+ # based on configured tracing mode.
16
+ #
17
+ # After the rack layer passes on to the following layers (Rails, Sinatra,
18
+ # Padrino, Grape), then the instrumentation downstream will
19
+ # automatically detect whether this is a sampled request or not
20
+ # and act accordingly.
21
+ #
22
+ class Rack
23
+ include SolarWindsAPM::SDK::TraceContextHeaders
24
+
25
+ attr_reader :app
26
+
27
+ def initialize(app)
28
+ @app = app
29
+ end
30
+
31
+ def call(env)
32
+
33
+ # In the case of nested Ruby apps such as Grape inside of Rails
34
+ # or Grape inside of Grape, each app has it's own instance
35
+ # of rack middleware. We want to avoid tracing rack more than once
36
+ return @app.call(env) if SolarWindsAPM.tracing? && SolarWindsAPM.layer == :rack
37
+
38
+ # there may be a legit existing context that we need to continue
39
+ # we need to check if we marked a start in env
40
+ # env is specific per request
41
+ existing_context = false
42
+ if SolarWindsAPM::Context.isValid
43
+ existing_context = env['SW_APM_TRACE_STARTED'] == 'true'
44
+
45
+ if existing_context
46
+ # include or override tracecontext in env, so that the current context gets continued
47
+ add_tracecontext_headers(env)
48
+ else
49
+ SolarWindsAPM::Context.clear
50
+ end
51
+ end
52
+
53
+ SolarWindsAPM.transaction_name = nil
54
+
55
+ url = env['PATH_INFO']
56
+ options = SolarWindsAPM::XTraceOptions.new(env['HTTP_X_TRACE_OPTIONS'], env['HTTP_X_TRACE_OPTIONS_SIGNATURE'])
57
+
58
+ # store incoming information in a thread local variable
59
+ settings = SolarWindsAPM::TransactionSettings.new(url, env, options)
60
+
61
+ profile_spans = SolarWindsAPM::Config['profiling'] == :enabled ? 1 : -1
62
+
63
+ response =
64
+ propagate_tracecontext(env, settings) do
65
+ sample(env, settings, options, profile_spans) do
66
+ SolarWindsAPM::Profiling.run do
67
+ SolarWindsAPM::TransactionMetrics.metrics(env, settings) do
68
+ @app.call(env)
69
+ end
70
+ end
71
+ end
72
+ end || [500, {}, nil]
73
+ options.add_response_header(response[1], settings)
74
+
75
+ unless existing_context
76
+ SolarWindsAPM::Context.clear
77
+ SolarWindsAPM.trace_context = nil
78
+ end
79
+ response
80
+ rescue
81
+ unless existing_context
82
+ SolarWindsAPM::Context.clear
83
+ SolarWindsAPM.trace_context = nil
84
+ end
85
+ raise
86
+ # can't use ensure for Context.clearing, because the Grape middleware
87
+ # needs the context in case of an error, it is somewhat convoluted ...
88
+ end
89
+
90
+ def self.noop?
91
+ false
92
+ end
93
+
94
+ private
95
+
96
+ def collect(env)
97
+ req = ::Rack::Request.new(env)
98
+ report_kvs = {}
99
+
100
+ begin
101
+ report_kvs[:'HTTP-Host'] = req.host
102
+ report_kvs[:Port] = req.port
103
+ report_kvs[:Proto] = req.scheme
104
+ report_kvs[:Method] = req.request_method
105
+ report_kvs[:AJAX] = true if req.xhr?
106
+ report_kvs[:ClientIP] = req.ip
107
+
108
+ if SolarWindsAPM::Config[:rack][:log_args]
109
+ report_kvs[:'Query-String'] = ::CGI.unescape(req.query_string) unless req.query_string.empty?
110
+ end
111
+
112
+ report_kvs[:URL] = SolarWindsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
113
+ report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:rack][:collect_backtraces]
114
+
115
+ # Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
116
+ report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
117
+ report_kvs[:'Request-Start'] = env['HTTP_X_QUEUE_START'] if env.key?('HTTP_X_QUEUE_START')
118
+ report_kvs[:'Queue-Time'] = env['HTTP_X_QUEUE_TIME'] if env.key?('HTTP_X_QUEUE_TIME')
119
+
120
+ report_kvs[:'Forwarded-For'] = env['HTTP_X_FORWARDED_FOR'] if env.key?('HTTP_X_FORWARDED_FOR')
121
+ report_kvs[:'Forwarded-Host'] = env['HTTP_X_FORWARDED_HOST'] if env.key?('HTTP_X_FORWARDED_HOST')
122
+ report_kvs[:'Forwarded-Proto'] = env['HTTP_X_FORWARDED_PROTO'] if env.key?('HTTP_X_FORWARDED_PROTO')
123
+ report_kvs[:'Forwarded-Port'] = env['HTTP_X_FORWARDED_PORT'] if env.key?('HTTP_X_FORWARDED_PORT')
124
+
125
+ report_kvs[:'Ruby.SolarWindsAPM.Version'] = SolarWindsAPM::Version::STRING
126
+ report_kvs[:ProcessID] = Process.pid
127
+ report_kvs[:ThreadID] = Thread.current.to_s[/0x\w*/]
128
+ rescue StandardError => e
129
+ # Discard any potential exceptions. Debug log and report whatever we can.
130
+ SolarWindsAPM.logger.debug "[solarwinds_apm/debug] Rack KV collection error: #{e.inspect}"
131
+ end
132
+ report_kvs
133
+ end
134
+
135
+ # this adds x-trace info to the request and return header
136
+ # if it is not a request for an asset (defined in config file as 'dnt')
137
+ def propagate_tracecontext(env, settings)
138
+ return yield unless settings.do_propagate
139
+
140
+ if SolarWindsAPM.trace_context&.tracestring
141
+ # creating a dup because we are modifying it when setting/unsetting the sampling bit
142
+ tracestring_dup = SolarWindsAPM.trace_context.tracestring.dup
143
+ if settings.do_sample
144
+ SolarWindsAPM::TraceString.set_sampled(tracestring_dup)
145
+ else
146
+ SolarWindsAPM::TraceString.unset_sampled(tracestring_dup)
147
+ end
148
+ env['HTTP_TRACEPARENT'] = tracestring_dup
149
+ env['HTTP_TRACESTATE'] = SolarWindsAPM::TraceState.add_sw_member(
150
+ SolarWindsAPM.trace_context&.tracestate,
151
+ SolarWindsAPM::TraceString.span_id_flags(tracestring_dup)
152
+ )
153
+ end
154
+
155
+ status, headers, response = yield
156
+
157
+ # TODO this will be finalized when we have a spec for w3c response headers
158
+ headers ||= {}
159
+ headers['X-Trace'] = SolarWindsAPM::Context.toString if SolarWindsAPM::Context.isValid
160
+
161
+ [status, headers, response]
162
+ end
163
+
164
+ def sample(env, settings, options, profile_spans)
165
+ if settings.do_sample
166
+ begin
167
+ report_kvs = collect(env)
168
+ settings.add_kvs(report_kvs)
169
+ options&.add_kvs(report_kvs, settings)
170
+
171
+ SolarWindsAPM::API.log_start(:rack, report_kvs, env, settings)
172
+ # mark the trace as started for this request
173
+ env['SW_APM_TRACE_STARTED'] = 'true'
174
+
175
+ status, headers, response = yield
176
+
177
+ SolarWindsAPM::API.log_exit(:rack, { Status: status,
178
+ TransactionName: SolarWindsAPM.transaction_name,
179
+ ProfileSpans: profile_spans })
180
+
181
+ [status, headers, response]
182
+ rescue Exception => e
183
+ # it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
184
+ SolarWindsAPM::API.log_exception(:rack, e)
185
+ SolarWindsAPM::API.log_exit(:rack, { Status: status,
186
+ TransactionName: SolarWindsAPM.transaction_name,
187
+ ProfileSpans: profile_spans
188
+ })
189
+ raise
190
+ end
191
+ else
192
+ SolarWindsAPM::API.create_nontracing_context(SolarWindsAPM.trace_context.tracestring)
193
+ yield
194
+ end
195
+ end
196
+
197
+ end
198
+ end
199
+ else
200
+ module SolarWindsAPM
201
+ class Rack
202
+ attr_reader :app
203
+
204
+ def initialize(app)
205
+ @app = app
206
+ end
207
+
208
+ def call(env)
209
+ @app.call(env)
210
+ end
211
+
212
+ def self.noop?
213
+ true
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,35 @@
1
+ # Copyright (c) 2020 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ module RackCacheContext
6
+
7
+ ###
8
+ # This adds a controller.action like transaction name for
9
+ # requests directly served from the cache without involving a controller.
10
+ # The resulting transaction name is `rack-cache.<cache-store>`,
11
+ # e.g. `rack-cache.memcached`
12
+ #
13
+ # It is not a full instrumentation, no span is added.
14
+ #
15
+ def call!(env)
16
+ metastore_type = begin
17
+ if options['rack-cache.metastore']
18
+ options['rack-cache.metastore'].match(/^([^\:]*)\:/)[1]
19
+ end || 'unknown_store'
20
+ rescue
21
+ 'unknown_store'
22
+ end
23
+
24
+ env['solarwinds_apm.action'] = metastore_type
25
+ env['solarwinds_apm.controller'] = 'rack-cache'
26
+
27
+ super
28
+ end
29
+ end
30
+ end
31
+
32
+ if SolarWindsAPM::Config[:rack_cache][:transaction_name] && defined?(Rack::Cache::Context)
33
+ SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting rack_cache' if SolarWindsAPM::Config[:verbose]
34
+ Rack::Cache::Context.send(:prepend, ::SolarWindsAPM::RackCacheContext)
35
+ end
@@ -0,0 +1,273 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ module Inst
6
+ module Redis
7
+ module Client
8
+ # The operations listed in this constant skip collecting KVKey
9
+ NO_KEY_OPS = [:auth, :keys, :randomkey, :scan, :sdiff, :sdiffstore, :sinter,
10
+ :sinterstore, :smove, :sunion, :sunionstore, :zinterstore,
11
+ :zunionstore, :publish, :select, :eval, :evalsha, :script].freeze
12
+
13
+ # Instead of a giant switch statement, we use a hash constant to map out what
14
+ # KVs need to be collected for each of the many many Redis operations
15
+ # Hash formatting by undiagnosed OCD
16
+ KV_COLLECT_MAP = {
17
+ :brpoplpush => { :destination => 2 }, :rpoplpush => { :destination => 2 },
18
+ :sdiffstore => { :destination => 1 }, :sinterstore => { :destination => 1 },
19
+ :sunionstore => { :destination => 1 }, :zinterstore => { :destination => 1 },
20
+ :zunionstore => { :destination => 1 }, :publish => { :channel => 1 },
21
+ :incrby => { :increment => 2 }, :incrbyfloat => { :increment => 2 },
22
+ :pexpire => { :milliseconds => 2 }, :pexpireat => { :milliseconds => 2 },
23
+ :expireat => { :timestamp => 2 }, :decrby => { :decrement => 2 },
24
+ :psetex => { :ttl => 2 }, :restore => { :ttl => 2 },
25
+ :setex => { :ttl => 2 }, :setnx => { :ttl => 2 },
26
+ :move => { :db => 2 }, :select => { :db => 1 },
27
+ :lindex => { :index => 2 }, :getset => { :value => 2 },
28
+ :keys => { :pattern => 1 }, :expire => { :seconds => 2 },
29
+ :rename => { :newkey => 2 }, :renamenx => { :newkey => 2 },
30
+ :getbit => { :offset => 2 }, :setbit => { :offset => 2 },
31
+ :setrange => { :offset => 2 }, :evalsha => { :sha => 1 },
32
+ :getrange => { :start => 2, :end => 3 },
33
+ :zrange => { :start => 2, :end => 3 },
34
+ :bitcount => { :start => 2, :stop => 3 },
35
+ :lrange => { :start => 2, :stop => 3 },
36
+ :zrevrange => { :start => 2, :stop => 3 },
37
+ :hincrby => { :field => 2, :increment => 3 },
38
+ :smove => { :source => 1, :destination => 2 },
39
+ :bitop => { :operation => 1, :destkey => 2 },
40
+ :hincrbyfloat => { :field => 2, :increment => 3 },
41
+ :zremrangebyrank => { :start => 2, :stop => 3 }
42
+ }.freeze
43
+
44
+ # The following operations don't require any special handling. For these,
45
+ # we only collect KVKey and KVOp
46
+ #
47
+ # :append, :blpop, :brpop, :decr, :del, :dump, :exists,
48
+ # :hgetall, :hkeys, :hlen, :hvals, :hmset, :incr, :linsert,
49
+ # :llen, :lpop, :lpush, :lpushx, :lrem, :lset, :ltrim,
50
+ # :persist, :pttl, :hscan, :rpop, :rpush, :rpushx, :sadd,
51
+ # :scard, :sismember, :smembers, :strlen, :sort, :spop,
52
+ # :srandmember, :srem, :sscan, :ttl, :type, :zadd, :zcard,
53
+ # :zcount, :zincrby, :zrangebyscore, :zrank, :zrem,
54
+ # :zremrangebyscore, :zrevrank, :zrevrangebyscore, :zscore
55
+ #
56
+ # For the operations in NO_KEY_OPS (above) we only collect
57
+ # KVOp (no KVKey)
58
+
59
+ def self.included(klass)
60
+ # We wrap two of the Redis methods to instrument
61
+ # operations
62
+ SolarWindsAPM::Util.method_alias(klass, :call, ::Redis::Client)
63
+ SolarWindsAPM::Util.method_alias(klass, :call_pipeline, ::Redis::Client)
64
+ end
65
+
66
+ # Given any Redis operation command array, this method
67
+ # extracts the Key/Values to report to the SolarWinds # dashboard.
68
+ #
69
+ # @param command [Array] the Redis operation array
70
+ # @param r [Return] the return value from the operation
71
+ # @return [Hash] the Key/Values to report
72
+ def extract_trace_details(command, r)
73
+ kvs = {}
74
+ op = command.first
75
+
76
+ kvs[:KVOp] = command[0]
77
+ kvs[:RemoteHost] = @options[:host]
78
+ unless NO_KEY_OPS.include?(op) || op == :del && command[1..-1].flatten.count > 1
79
+ if command[1].is_a?(Array)
80
+ kvs[:KVKey] = command[1].first
81
+ else
82
+ kvs[:KVKey] = command[1]
83
+ end
84
+ end
85
+
86
+ if KV_COLLECT_MAP[op]
87
+ # Extract KVs from command for this op
88
+ KV_COLLECT_MAP[op].each { |k, v| kvs[k] = command[v] }
89
+ else
90
+ # This case statement handle special cases not handled
91
+ # by KV_COLLECT_MAP
92
+ case op
93
+ when :set
94
+ if command.count > 3
95
+ if command[3].is_a?(Hash)
96
+ options = command[3]
97
+ kvs[:ex] = options[:ex] if options.key?(:ex)
98
+ kvs[:px] = options[:px] if options.key?(:px)
99
+ kvs[:nx] = options[:nx] if options.key?(:nx)
100
+ kvs[:xx] = options[:xx] if options.key?(:xx)
101
+ else
102
+ options = command[3..-1]
103
+ until (opts = options.shift(2)).empty?
104
+ case opts[0]
105
+ when 'EX' then; kvs[:ex] = opts[1]
106
+ when 'PX' then; kvs[:px] = opts[1]
107
+ when 'NX' then; kvs[:nx] = opts[1]
108
+ when 'XX' then; kvs[:xx] = opts[1]
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ when :get
115
+ kvs[:KVHit] = r.nil? ? 0 : 1
116
+
117
+ when :hdel, :hexists, :hget, :hset, :hsetnx
118
+ kvs[:field] = command[2] unless command[2].is_a?(Array)
119
+ if op == :hget
120
+ kvs[:KVHit] = r.nil? ? 0 : 1
121
+ end
122
+
123
+ when :eval
124
+ if command[1].length > 1024
125
+ kvs[:Script] = command[1][0..1023] + '(...snip...)'
126
+ else
127
+ kvs[:Script] = command[1]
128
+ end
129
+
130
+ when :script
131
+ kvs[:subcommand] = command[1]
132
+ kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:redis][:collect_backtraces]
133
+ if command[1] == 'load'
134
+ if command[1].length > 1024
135
+ kvs[:Script] = command[2][0..1023] + '(...snip...)'
136
+ else
137
+ kvs[:Script] = command[2]
138
+ end
139
+ elsif command[1] == :exists
140
+ if command[2].is_a?(Array)
141
+ kvs[:KVKey] = command[2].inspect
142
+ else
143
+ kvs[:KVKey] = command[2]
144
+ end
145
+ end
146
+
147
+ when :mget
148
+ if command[1].is_a?(Array)
149
+ kvs[:KVKeyCount] = command[1].count
150
+ else
151
+ kvs[:KVKeyCount] = command.count - 1
152
+ end
153
+ values = r.select { |i| i }
154
+ kvs[:KVHitCount] = values.count
155
+
156
+ when :hmget
157
+ kvs[:KVKeyCount] = command.count - 2
158
+ values = r.select { |i| i }
159
+ kvs[:KVHitCount] = values.count
160
+
161
+ when :mset, :msetnx
162
+ if command[1].is_a?(Array)
163
+ kvs[:KVKeyCount] = command[1].count / 2
164
+ else
165
+ kvs[:KVKeyCount] = (command.count - 1) / 2
166
+ end
167
+ end # case op
168
+ end # if KV_COLLECT_MAP[op]
169
+ rescue StandardError => e
170
+ SolarWindsAPM.logger.debug "[solarwinds_apm/redis] Error collecting redis KVs: #{e.message}"
171
+ SolarWindsAPM.logger.debug e.backtrace.join('\n')
172
+ ensure
173
+ return kvs
174
+ end
175
+
176
+ # Extracts the Key/Values to report from a pipelined
177
+ # call to the SolarWinds dashboard.
178
+ #
179
+ # @param pipeline [Redis::Pipeline] the Redis pipeline instance
180
+ # @return [Hash] the Key/Values to report
181
+ def extract_pipeline_details(pipeline)
182
+ kvs = {}
183
+
184
+ kvs[:RemoteHost] = @options[:host]
185
+ kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:redis][:collect_backtraces]
186
+
187
+ command_count = pipeline.commands.count
188
+ kvs[:KVOpCount] = command_count
189
+
190
+ kvs[:KVOp] = if pipeline.commands.first == :multi
191
+ :multi
192
+ else
193
+ :pipeline
194
+ end
195
+
196
+ # Report pipelined operations if the number
197
+ # of ops is reasonable
198
+ if command_count < 12
199
+ ops = []
200
+ pipeline.commands.each do |c|
201
+ ops << c.first
202
+ end
203
+ kvs[:KVOps] = ops.join(', ')
204
+ end
205
+ rescue StandardError => e
206
+ SolarWindsAPM.logger.debug "[solarwinds_apm/debug] Error extracting pipelined commands: #{e.message}"
207
+ SolarWindsAPM.logger.debug e.backtrace
208
+ ensure
209
+ return kvs
210
+ end
211
+
212
+ #
213
+ # The wrapper method for Redis::Client.call. Here
214
+ # (when tracing) we capture KVs to report and pass
215
+ # the call along
216
+ #
217
+ def call_with_sw_apm(command, &block)
218
+ if SolarWindsAPM.tracing?
219
+ SolarWindsAPM::API.log_entry(:redis, {})
220
+
221
+ begin
222
+ r = call_without_sw_apm(command, &block)
223
+ report_kvs = extract_trace_details(command, r)
224
+ r
225
+ rescue StandardError => e
226
+ SolarWindsAPM::API.log_exception(:redis, e)
227
+ raise
228
+ ensure
229
+ SolarWindsAPM::API.log_exit(:redis, report_kvs)
230
+ end
231
+
232
+ else
233
+ call_without_sw_apm(command, &block)
234
+ end
235
+ end
236
+
237
+ #
238
+ # The wrapper method for Redis::Client.call_pipeline. Here
239
+ # (when tracing) we capture KVs to report and pass the call along
240
+ #
241
+ def call_pipeline_with_sw_apm(pipeline)
242
+ if SolarWindsAPM.tracing?
243
+ # Fall back to the raw tracing API so we can pass KVs
244
+ # back on exit (a limitation of the SolarWindsAPM::API.trace
245
+ # block method) This removes the need for an info
246
+ # event to send additonal KVs
247
+ SolarWindsAPM::API.log_entry(:redis, {})
248
+
249
+ report_kvs = extract_pipeline_details(pipeline)
250
+
251
+ begin
252
+ call_pipeline_without_sw_apm(pipeline)
253
+ rescue StandardError => e
254
+ SolarWindsAPM::API.log_exception(:redis, e)
255
+ raise
256
+ ensure
257
+ SolarWindsAPM::API.log_exit(:redis, report_kvs)
258
+ end
259
+ else
260
+ call_pipeline_without_sw_apm(pipeline)
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ if SolarWindsAPM::Config[:redis][:enabled]
269
+ if defined?(Redis) && Gem::Version.new(Redis::VERSION) >= Gem::Version.new('3.0.0')
270
+ SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting redis' if SolarWindsAPM::Config[:verbose]
271
+ SolarWindsAPM::Util.send_include(Redis::Client, SolarWindsAPM::Inst::Redis::Client)
272
+ end
273
+ end
@@ -0,0 +1,129 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ require 'socket'
5
+ require 'json'
6
+
7
+ module SolarWindsAPM
8
+ module Inst
9
+ module ResqueClient
10
+
11
+ self.include SolarWindsAPM::SDK::TraceContextHeaders
12
+
13
+ def extract_trace_details(klass, args)
14
+ report_kvs = {}
15
+
16
+ begin
17
+ report_kvs[:Spec] = :pushq
18
+ report_kvs[:Flavor] = :resque
19
+ report_kvs[:JobName] = klass.to_s
20
+
21
+ if SolarWindsAPM::Config[:resqueclient][:log_args]
22
+ kv_args = args.to_json
23
+
24
+ # Limit the argument json string to 1024 bytes
25
+ if kv_args.length > 1024
26
+ report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
27
+ else
28
+ report_kvs[:Args] = kv_args
29
+ end
30
+ end
31
+ report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:resqueclient][:collect_backtraces]
32
+ report_kvs[:Queue] = klass.instance_variable_get(:@queue)
33
+ rescue => e
34
+ SolarWindsAPM.logger.debug "[solarwinds_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if SolarWindsAPM::Config[:verbose]
35
+ end
36
+
37
+ report_kvs
38
+ end
39
+
40
+ def push(queue, item)
41
+ if SolarWindsAPM.tracing?
42
+ report_kvs = extract_trace_details(item[:class], item[:args])
43
+ report_kvs[:Queue] = queue.to_s if queue
44
+
45
+ SolarWindsAPM::SDK.trace(:'resque-client', kvs: report_kvs) do
46
+ add_tracecontext_headers(item)
47
+ super
48
+ end
49
+ else
50
+ super
51
+ end
52
+ end
53
+
54
+ def dequeue(klass, *args)
55
+ if SolarWindsAPM.tracing?
56
+ report_kvs = extract_trace_details(klass, args)
57
+ SolarWindsAPM::SDK.trace(:'resque-client', kvs: report_kvs) do
58
+ super(klass, *args)
59
+ end
60
+ else
61
+ super(klass, *args)
62
+ end
63
+ end
64
+ end
65
+
66
+ module ResqueJob
67
+
68
+ def perform
69
+ report_kvs = {}
70
+
71
+ begin
72
+ report_kvs[:Spec] = :job
73
+ report_kvs[:Flavor] = :resque
74
+ report_kvs[:JobName] = payload['class'].to_s
75
+ report_kvs[:Queue] = queue.to_s
76
+
77
+ # Set these keys for the ability to separate out
78
+ # background tasks into a separate app on the server-side UI
79
+
80
+ report_kvs[:'HTTP-Host'] = Socket.gethostname
81
+ report_kvs[:Controller] = "Resque_#{queue}"
82
+ report_kvs[:Action] = payload['class'].to_s
83
+ report_kvs[:URL] = "/resque/#{queue}/#{payload['class']}"
84
+
85
+ if SolarWindsAPM::Config[:resqueworker][:log_args]
86
+ kv_args = payload['args'].to_json
87
+
88
+ # Limit the argument json string to 1024 bytes
89
+ if kv_args.length > 1024
90
+ report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
91
+ else
92
+ report_kvs[:Args] = kv_args
93
+ end
94
+ end
95
+
96
+ report_kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:resqueworker][:collect_backtraces]
97
+ rescue => e
98
+ SolarWindsAPM.logger.debug "[solarwinds_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if SolarWindsAPM::Config[:verbose]
99
+ end
100
+
101
+ SolarWindsAPM::SDK.start_trace('resque-worker', kvs: report_kvs, headers: payload) do
102
+ super
103
+ end
104
+
105
+ def fail(exception)
106
+ if SolarWindsAPM.tracing?
107
+ SolarWindsAPM::API.log_exception(:resque, exception)
108
+ end
109
+ super(exception)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ if defined?(::Resque)
117
+ SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting resque' if SolarWindsAPM::Config[:verbose]
118
+
119
+ if SolarWindsAPM::Config[:resqueclient][:enabled]
120
+ ::Resque.singleton_class.prepend(SolarWindsAPM::Inst::ResqueClient)
121
+ ::Resque.singleton_class.prepend(SolarWindsAPM::Inst::ResqueClient)
122
+ end
123
+
124
+ if SolarWindsAPM::Config[:resqueclient][:enabled] || SolarWindsAPM::Config[:resqueworker][:enabled]
125
+ Resque::Job.prepend(SolarWindsAPM::Inst::ResqueJob)
126
+ end
127
+ end
128
+
129
+
@@ -0,0 +1,43 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ module Inst
6
+ module RestClientRequest
7
+ include SolarWindsAPM::SDK::TraceContextHeaders
8
+
9
+ ##
10
+ # execute
11
+ #
12
+ # The wrapper method for RestClient::Request.execute
13
+ #
14
+ def execute(&block)
15
+ unless SolarWindsAPM.tracing?
16
+ add_tracecontext_headers(@processed_headers)
17
+ return super(&block)
18
+ end
19
+
20
+ begin
21
+ kvs = {}
22
+ kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:rest_client][:collect_backtraces]
23
+ SolarWindsAPM::API.log_entry('rest-client', kvs)
24
+
25
+ add_tracecontext_headers(@processed_headers)
26
+
27
+ # The core rest-client call
28
+ super(&block)
29
+ rescue => e
30
+ SolarWindsAPM::API.log_exception('rest-client', e)
31
+ raise e
32
+ ensure
33
+ SolarWindsAPM::API.log_exit('rest-client')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ if defined?(RestClient) && SolarWindsAPM::Config[:rest_client][:enabled]
41
+ SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting rest-client' if SolarWindsAPM::Config[:verbose]
42
+ RestClient::Request.prepend(SolarWindsAPM::Inst::RestClientRequest)
43
+ end