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,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