appoptics_apm_mnfst 4.5.2
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/.gitignore +29 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +121 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +33 -0
- data/Gemfile +29 -0
- data/LICENSE +193 -0
- data/README.md +393 -0
- data/Rakefile +230 -0
- data/appoptics_apm.gemspec +61 -0
- data/bin/appoptics_apm_config +15 -0
- data/build_gem.sh +15 -0
- data/build_gem_upload_to_packagecloud.sh +20 -0
- data/examples/SDK/01_basic_tracing.rb +67 -0
- data/examples/carrying_context.rb +220 -0
- data/ext/oboe_metal/extconf.rb +114 -0
- data/ext/oboe_metal/lib/.keep +0 -0
- data/ext/oboe_metal/noop/noop.c +7 -0
- data/ext/oboe_metal/src/VERSION +1 -0
- data/init.rb +4 -0
- data/lib/appoptics_apm.rb +76 -0
- data/lib/appoptics_apm/api.rb +20 -0
- data/lib/appoptics_apm/api/layerinit.rb +41 -0
- data/lib/appoptics_apm/api/logging.rb +375 -0
- data/lib/appoptics_apm/api/memcache.rb +37 -0
- data/lib/appoptics_apm/api/metrics.rb +55 -0
- data/lib/appoptics_apm/api/profiling.rb +203 -0
- data/lib/appoptics_apm/api/tracing.rb +53 -0
- data/lib/appoptics_apm/api/util.rb +122 -0
- data/lib/appoptics_apm/base.rb +230 -0
- data/lib/appoptics_apm/config.rb +254 -0
- data/lib/appoptics_apm/frameworks/grape.rb +97 -0
- data/lib/appoptics_apm/frameworks/padrino.rb +108 -0
- data/lib/appoptics_apm/frameworks/rails.rb +94 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +55 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +58 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +108 -0
- data/lib/appoptics_apm/frameworks/sinatra.rb +125 -0
- data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
- data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
- data/lib/appoptics_apm/inst/curb.rb +330 -0
- data/lib/appoptics_apm/inst/dalli.rb +85 -0
- data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
- data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
- data/lib/appoptics_apm/inst/excon.rb +125 -0
- data/lib/appoptics_apm/inst/faraday.rb +94 -0
- data/lib/appoptics_apm/inst/grpc_client.rb +162 -0
- data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
- data/lib/appoptics_apm/inst/http.rb +73 -0
- data/lib/appoptics_apm/inst/httpclient.rb +174 -0
- data/lib/appoptics_apm/inst/memcached.rb +86 -0
- data/lib/appoptics_apm/inst/mongo.rb +246 -0
- data/lib/appoptics_apm/inst/mongo2.rb +225 -0
- data/lib/appoptics_apm/inst/moped.rb +466 -0
- data/lib/appoptics_apm/inst/rack.rb +199 -0
- data/lib/appoptics_apm/inst/redis.rb +275 -0
- data/lib/appoptics_apm/inst/resque.rb +151 -0
- data/lib/appoptics_apm/inst/rest-client.rb +48 -0
- data/lib/appoptics_apm/inst/sequel.rb +178 -0
- data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
- data/lib/appoptics_apm/inst/sidekiq-worker.rb +65 -0
- data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
- data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
- data/lib/appoptics_apm/instrumentation.rb +22 -0
- data/lib/appoptics_apm/legacy_method_profiling.rb +90 -0
- data/lib/appoptics_apm/loading.rb +65 -0
- data/lib/appoptics_apm/logger.rb +42 -0
- data/lib/appoptics_apm/method_profiling.rb +33 -0
- data/lib/appoptics_apm/noop/README.md +9 -0
- data/lib/appoptics_apm/noop/context.rb +26 -0
- data/lib/appoptics_apm/noop/metadata.rb +22 -0
- data/lib/appoptics_apm/ruby.rb +35 -0
- data/lib/appoptics_apm/sdk/custom_metrics.rb +92 -0
- data/lib/appoptics_apm/sdk/tracing.rb +315 -0
- data/lib/appoptics_apm/support.rb +119 -0
- data/lib/appoptics_apm/test.rb +94 -0
- data/lib/appoptics_apm/thread_local.rb +26 -0
- data/lib/appoptics_apm/util.rb +319 -0
- data/lib/appoptics_apm/version.rb +15 -0
- data/lib/appoptics_apm/xtrace.rb +103 -0
- data/lib/joboe_metal.rb +212 -0
- data/lib/oboe.rb +7 -0
- data/lib/oboe/README +2 -0
- data/lib/oboe/backward_compatibility.rb +80 -0
- data/lib/oboe/inst/rack.rb +11 -0
- data/lib/oboe_metal.rb +198 -0
- data/lib/rails/generators/appoptics_apm/install_generator.rb +45 -0
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +265 -0
- data/yardoc_frontpage.md +26 -0
- metadata +266 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'cgi'
|
|
6
|
+
|
|
7
|
+
if AppOpticsAPM.loaded
|
|
8
|
+
module AppOpticsAPM
|
|
9
|
+
##
|
|
10
|
+
# AppOpticsAPM::Rack
|
|
11
|
+
#
|
|
12
|
+
# The AppOpticsAPM::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. (to instrument or not)
|
|
21
|
+
#
|
|
22
|
+
class Rack
|
|
23
|
+
attr_reader :app
|
|
24
|
+
|
|
25
|
+
def initialize(app)
|
|
26
|
+
@app = app
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call(env)
|
|
30
|
+
# In the case of nested Ruby apps such as Grape inside of Rails
|
|
31
|
+
# or Grape inside of Grape, each app has it's own instance
|
|
32
|
+
# of rack middleware. We avoid tracing rack more than once and
|
|
33
|
+
# instead start instrumenting from the first rack pass.
|
|
34
|
+
return call_app(env) if AppOpticsAPM.tracing? && AppOpticsAPM.layer == :rack
|
|
35
|
+
|
|
36
|
+
# if we already have a context, we don't want to send metrics in the end
|
|
37
|
+
return sampling_call(env) if AppOpticsAPM::Context.isValid
|
|
38
|
+
|
|
39
|
+
# else we also send metrics
|
|
40
|
+
metrics_sampling_call(env)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.noop?
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def collect(req, env)
|
|
50
|
+
report_kvs = {}
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
report_kvs[:'HTTP-Host'] = req.host
|
|
54
|
+
report_kvs[:Port] = req.port
|
|
55
|
+
report_kvs[:Proto] = req.scheme
|
|
56
|
+
report_kvs[:Method] = req.request_method
|
|
57
|
+
report_kvs[:AJAX] = true if req.xhr?
|
|
58
|
+
report_kvs[:ClientIP] = req.ip
|
|
59
|
+
|
|
60
|
+
if AppOpticsAPM::Config[:rack][:log_args]
|
|
61
|
+
report_kvs[:'Query-String'] = ::CGI.unescape(req.query_string) unless req.query_string.empty?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:rack][:collect_backtraces]
|
|
65
|
+
|
|
66
|
+
# Report any request queue'ing headers. Report as 'Request-Start' or the summed Queue-Time
|
|
67
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_REQUEST_START'] if env.key?('HTTP_X_REQUEST_START')
|
|
68
|
+
report_kvs[:'Request-Start'] = env['HTTP_X_QUEUE_START'] if env.key?('HTTP_X_QUEUE_START')
|
|
69
|
+
report_kvs[:'Queue-Time'] = env['HTTP_X_QUEUE_TIME'] if env.key?('HTTP_X_QUEUE_TIME')
|
|
70
|
+
|
|
71
|
+
report_kvs[:'Forwarded-For'] = env['HTTP_X_FORWARDED_FOR'] if env.key?('HTTP_X_FORWARDED_FOR')
|
|
72
|
+
report_kvs[:'Forwarded-Host'] = env['HTTP_X_FORWARDED_HOST'] if env.key?('HTTP_X_FORWARDED_HOST')
|
|
73
|
+
report_kvs[:'Forwarded-Proto'] = env['HTTP_X_FORWARDED_PROTO'] if env.key?('HTTP_X_FORWARDED_PROTO')
|
|
74
|
+
report_kvs[:'Forwarded-Port'] = env['HTTP_X_FORWARDED_PORT'] if env.key?('HTTP_X_FORWARDED_PORT')
|
|
75
|
+
|
|
76
|
+
report_kvs[:'Ruby.AppOptics.Version'] = AppOpticsAPM::Version::STRING
|
|
77
|
+
report_kvs[:ProcessID] = Process.pid
|
|
78
|
+
report_kvs[:ThreadID] = Thread.current.to_s[/0x\w*/]
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
# Discard any potential exceptions. Debug log and report whatever we can.
|
|
81
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/debug] Rack KV collection error: #{e.inspect}"
|
|
82
|
+
end
|
|
83
|
+
report_kvs
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def call_app(env)
|
|
87
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/rack] Rack skipped!"
|
|
88
|
+
@app.call(env)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# in this case we have an existing context
|
|
92
|
+
def sampling_call(env)
|
|
93
|
+
req = ::Rack::Request.new(env)
|
|
94
|
+
report_kvs = {}
|
|
95
|
+
report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
|
|
96
|
+
|
|
97
|
+
AppOpticsAPM::API.trace(:rack, report_kvs) do
|
|
98
|
+
report_kvs = collect(req, env)
|
|
99
|
+
|
|
100
|
+
# We log an info event with the HTTP KVs found in AppOpticsAPM::Rack.collect
|
|
101
|
+
# This is done here so in the case of stacks that try/catch/abort
|
|
102
|
+
# (looking at you Grape) we're sure the KVs get reported now as
|
|
103
|
+
# this code may not be returned to later.
|
|
104
|
+
AppOpticsAPM::API.log_info(:rack, report_kvs)
|
|
105
|
+
@app.call(env)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def metrics_sampling_call(env)
|
|
110
|
+
start = Time.now
|
|
111
|
+
AppOpticsAPM.transaction_name = nil
|
|
112
|
+
req = ::Rack::Request.new(env)
|
|
113
|
+
req_url = req.url # saving it here because rails3.2 overrides it when there is a 500 error
|
|
114
|
+
status = 500 # initialize with 500
|
|
115
|
+
|
|
116
|
+
report_kvs = collect(req, env)
|
|
117
|
+
report_kvs[:URL] = AppOpticsAPM::Config[:rack][:log_args] ? ::CGI.unescape(req.fullpath) : ::CGI.unescape(req.path)
|
|
118
|
+
|
|
119
|
+
# Check for and validate X-Trace request header to pick up tracing context
|
|
120
|
+
xtrace = env.is_a?(Hash) ? env['HTTP_X_TRACE'] : nil
|
|
121
|
+
xtrace = AppOpticsAPM::XTrace.valid?(xtrace) ? xtrace : nil
|
|
122
|
+
|
|
123
|
+
# TODO JRUBY
|
|
124
|
+
# Under JRuby, JAppOpticsAPM may have already started a trace. Make note of this
|
|
125
|
+
# if so and don't clear context on log_end (see appoptics_apm/api/logging.rb)
|
|
126
|
+
# AppOpticsAPM.has_incoming_context = AppOpticsAPM.tracing?
|
|
127
|
+
# AppOpticsAPM.has_xtrace_header = xtrace
|
|
128
|
+
# AppOpticsAPM.is_continued_trace = AppOpticsAPM.has_incoming_context || AppOpticsAPM.has_xtrace_header
|
|
129
|
+
|
|
130
|
+
AppOpticsAPM::API.log_start(:rack, xtrace, report_kvs) unless AppOpticsAPM::Util.static_asset?(env['PATH_INFO'])
|
|
131
|
+
|
|
132
|
+
status, headers, response = @app.call(env)
|
|
133
|
+
confirmed_transaction_name = send_metrics(env, req, req_url, start, status)
|
|
134
|
+
xtrace = AppOpticsAPM::API.log_end(:rack, :Status => status, :TransactionName => confirmed_transaction_name)
|
|
135
|
+
|
|
136
|
+
# TODO revisit this JRUBY condition
|
|
137
|
+
# headers['X-Trace'] = xtrace if headers.is_a?(Hash) unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
|
|
138
|
+
headers['X-Trace'] = xtrace if headers.is_a?(Hash)
|
|
139
|
+
|
|
140
|
+
[status, headers, response]
|
|
141
|
+
rescue Exception => e
|
|
142
|
+
# it is ok to rescue Exception here because we are reraising it (we just need a chance to log_end)
|
|
143
|
+
AppOpticsAPM::API.log_exception(:rack, e)
|
|
144
|
+
confirmed_transaction_name ||= send_metrics(env, req, req_url, start, status)
|
|
145
|
+
xtrace = AppOpticsAPM::API.log_end(:rack, :Status => status, :TransactionName => confirmed_transaction_name)
|
|
146
|
+
|
|
147
|
+
# TODO revisit this JRUBY condition
|
|
148
|
+
# headers['X-Trace'] = xtrace if headers.is_a?(Hash) unless defined?(JRUBY_VERSION) && AppOpticsAPM.is_continued_trace?
|
|
149
|
+
headers['X-Trace'] = xtrace if headers.is_a?(Hash)
|
|
150
|
+
|
|
151
|
+
raise
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def send_metrics(env, req, req_url, start, status)
|
|
156
|
+
return if AppOpticsAPM::Util.static_asset?(env['PATH_INFO'])
|
|
157
|
+
|
|
158
|
+
status = status.to_i
|
|
159
|
+
error = status.between?(500,599) ? 1 : 0
|
|
160
|
+
duration =(1000 * 1000 * (Time.now - start)).round(0)
|
|
161
|
+
method = req.request_method
|
|
162
|
+
AppOpticsAPM::Span.createHttpSpan(transaction_name(env), req_url, domain(req), duration, status, method, error) || ''
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def domain(req)
|
|
166
|
+
if AppOpticsAPM::Config['transaction_name']['prepend_domain']
|
|
167
|
+
[80, 443].include?(req.port) ? req.host : "#{req.host}:#{req.port}"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def transaction_name(env)
|
|
172
|
+
if AppOpticsAPM.transaction_name
|
|
173
|
+
AppOpticsAPM.transaction_name
|
|
174
|
+
elsif env['appoptics_apm.controller'] && env['appoptics_apm.action']
|
|
175
|
+
[env['appoptics_apm.controller'], env['appoptics_apm.action']].join('.')
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
module AppOpticsAPM
|
|
183
|
+
class Rack
|
|
184
|
+
attr_reader :app
|
|
185
|
+
|
|
186
|
+
def initialize(app)
|
|
187
|
+
@app = app
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def call(env)
|
|
191
|
+
@app.call(env)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def self.noop?
|
|
195
|
+
true
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
module AppOpticsAPM
|
|
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
|
+
AppOpticsAPM::Util.method_alias(klass, :call, ::Redis::Client)
|
|
63
|
+
AppOpticsAPM::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 AppOptics
|
|
68
|
+
# dashboard.
|
|
69
|
+
#
|
|
70
|
+
# @param command [Array] the Redis operation array
|
|
71
|
+
# @param r [Return] the return value from the operation
|
|
72
|
+
# @return [Hash] the Key/Values to report
|
|
73
|
+
def extract_trace_details(command, r)
|
|
74
|
+
kvs = {}
|
|
75
|
+
op = command.first
|
|
76
|
+
|
|
77
|
+
kvs[:KVOp] = command[0]
|
|
78
|
+
kvs[:RemoteHost] = @options[:host]
|
|
79
|
+
|
|
80
|
+
unless NO_KEY_OPS.include?(op) || (command[1].is_a?(Array) && command[1].count > 1)
|
|
81
|
+
if command[1].is_a?(Array)
|
|
82
|
+
kvs[:KVKey] = command[1].first
|
|
83
|
+
else
|
|
84
|
+
kvs[:KVKey] = command[1]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if KV_COLLECT_MAP[op]
|
|
89
|
+
# Extract KVs from command for this op
|
|
90
|
+
KV_COLLECT_MAP[op].each { |k, v| kvs[k] = command[v] }
|
|
91
|
+
else
|
|
92
|
+
# This case statement handle special cases not handled
|
|
93
|
+
# by KV_COLLECT_MAP
|
|
94
|
+
case op
|
|
95
|
+
when :set
|
|
96
|
+
if command.count > 3
|
|
97
|
+
if command[3].is_a?(Hash)
|
|
98
|
+
options = command[3]
|
|
99
|
+
kvs[:ex] = options[:ex] if options.key?(:ex)
|
|
100
|
+
kvs[:px] = options[:px] if options.key?(:px)
|
|
101
|
+
kvs[:nx] = options[:nx] if options.key?(:nx)
|
|
102
|
+
kvs[:xx] = options[:xx] if options.key?(:xx)
|
|
103
|
+
else
|
|
104
|
+
options = command[3..-1]
|
|
105
|
+
until (opts = options.shift(2)).empty?
|
|
106
|
+
case opts[0]
|
|
107
|
+
when 'EX' then; kvs[:ex] = opts[1]
|
|
108
|
+
when 'PX' then; kvs[:px] = opts[1]
|
|
109
|
+
when 'NX' then; kvs[:nx] = opts[1]
|
|
110
|
+
when 'XX' then; kvs[:xx] = opts[1]
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
when :get
|
|
117
|
+
kvs[:KVHit] = r.nil? ? 0 : 1
|
|
118
|
+
|
|
119
|
+
when :hdel, :hexists, :hget, :hset, :hsetnx
|
|
120
|
+
kvs[:field] = command[2] unless command[2].is_a?(Array)
|
|
121
|
+
if op == :hget
|
|
122
|
+
kvs[:KVHit] = r.nil? ? 0 : 1
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
when :eval
|
|
126
|
+
if command[1].length > 1024
|
|
127
|
+
kvs[:Script] = command[1][0..1023] + '(...snip...)'
|
|
128
|
+
else
|
|
129
|
+
kvs[:Script] = command[1]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
when :script
|
|
133
|
+
kvs[:subcommand] = command[1]
|
|
134
|
+
kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:redis][:collect_backtraces]
|
|
135
|
+
if command[1] == 'load'
|
|
136
|
+
if command[1].length > 1024
|
|
137
|
+
kvs[:Script] = command[2][0..1023] + '(...snip...)'
|
|
138
|
+
else
|
|
139
|
+
kvs[:Script] = command[2]
|
|
140
|
+
end
|
|
141
|
+
elsif command[1] == :exists
|
|
142
|
+
if command[2].is_a?(Array)
|
|
143
|
+
kvs[:KVKey] = command[2].inspect
|
|
144
|
+
else
|
|
145
|
+
kvs[:KVKey] = command[2]
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
when :mget
|
|
150
|
+
if command[1].is_a?(Array)
|
|
151
|
+
kvs[:KVKeyCount] = command[1].count
|
|
152
|
+
else
|
|
153
|
+
kvs[:KVKeyCount] = command.count - 1
|
|
154
|
+
end
|
|
155
|
+
values = r.select { |i| i }
|
|
156
|
+
kvs[:KVHitCount] = values.count
|
|
157
|
+
|
|
158
|
+
when :hmget
|
|
159
|
+
kvs[:KVKeyCount] = command.count - 2
|
|
160
|
+
values = r.select { |i| i }
|
|
161
|
+
kvs[:KVHitCount] = values.count
|
|
162
|
+
|
|
163
|
+
when :mset, :msetnx
|
|
164
|
+
if command[1].is_a?(Array)
|
|
165
|
+
kvs[:KVKeyCount] = command[1].count / 2
|
|
166
|
+
else
|
|
167
|
+
kvs[:KVKeyCount] = (command.count - 1) / 2
|
|
168
|
+
end
|
|
169
|
+
end # case op
|
|
170
|
+
end # if KV_COLLECT_MAP[op]
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/redis] Error collecting redis KVs: #{e.message}"
|
|
173
|
+
AppOpticsAPM.logger.debug e.backtrace.join('\n')
|
|
174
|
+
ensure
|
|
175
|
+
return kvs
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Extracts the Key/Values to report from a pipelined
|
|
179
|
+
# call to the AppOptics dashboard.
|
|
180
|
+
#
|
|
181
|
+
# @param pipeline [Redis::Pipeline] the Redis pipeline instance
|
|
182
|
+
# @return [Hash] the Key/Values to report
|
|
183
|
+
def extract_pipeline_details(pipeline)
|
|
184
|
+
kvs = {}
|
|
185
|
+
|
|
186
|
+
kvs[:RemoteHost] = @options[:host]
|
|
187
|
+
kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:redis][:collect_backtraces]
|
|
188
|
+
|
|
189
|
+
command_count = pipeline.commands.count
|
|
190
|
+
kvs[:KVOpCount] = command_count
|
|
191
|
+
|
|
192
|
+
kvs[:KVOp] = if pipeline.commands.first == :multi
|
|
193
|
+
:multi
|
|
194
|
+
else
|
|
195
|
+
:pipeline
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Report pipelined operations if the number
|
|
199
|
+
# of ops is reasonable
|
|
200
|
+
if command_count < 12
|
|
201
|
+
ops = []
|
|
202
|
+
pipeline.commands.each do |c|
|
|
203
|
+
ops << c.first
|
|
204
|
+
end
|
|
205
|
+
kvs[:KVOps] = ops.join(', ')
|
|
206
|
+
end
|
|
207
|
+
rescue StandardError => e
|
|
208
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/debug] Error extracting pipelined commands: #{e.message}"
|
|
209
|
+
AppOpticsAPM.logger.debug e.backtrace
|
|
210
|
+
ensure
|
|
211
|
+
return kvs
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
#
|
|
215
|
+
# The wrapper method for Redis::Client.call. Here
|
|
216
|
+
# (when tracing) we capture KVs to report and pass
|
|
217
|
+
# the call along
|
|
218
|
+
#
|
|
219
|
+
def call_with_appoptics(command, &block)
|
|
220
|
+
if AppOpticsAPM.tracing?
|
|
221
|
+
AppOpticsAPM::API.log_entry(:redis, {})
|
|
222
|
+
|
|
223
|
+
begin
|
|
224
|
+
r = call_without_appoptics(command, &block)
|
|
225
|
+
report_kvs = extract_trace_details(command, r)
|
|
226
|
+
r
|
|
227
|
+
rescue StandardError => e
|
|
228
|
+
AppOpticsAPM::API.log_exception(:redis, e)
|
|
229
|
+
raise
|
|
230
|
+
ensure
|
|
231
|
+
AppOpticsAPM::API.log_exit(:redis, report_kvs)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
else
|
|
235
|
+
call_without_appoptics(command, &block)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
#
|
|
240
|
+
# The wrapper method for Redis::Client.call_pipeline. Here
|
|
241
|
+
# (when tracing) we capture KVs to report and pass the call along
|
|
242
|
+
#
|
|
243
|
+
def call_pipeline_with_appoptics(pipeline)
|
|
244
|
+
if AppOpticsAPM.tracing?
|
|
245
|
+
# Fall back to the raw tracing API so we can pass KVs
|
|
246
|
+
# back on exit (a limitation of the AppOpticsAPM::API.trace
|
|
247
|
+
# block method) This removes the need for an info
|
|
248
|
+
# event to send additonal KVs
|
|
249
|
+
AppOpticsAPM::API.log_entry(:redis, {})
|
|
250
|
+
|
|
251
|
+
report_kvs = extract_pipeline_details(pipeline)
|
|
252
|
+
|
|
253
|
+
begin
|
|
254
|
+
call_pipeline_without_appoptics(pipeline)
|
|
255
|
+
rescue StandardError => e
|
|
256
|
+
AppOpticsAPM::API.log_exception(:redis, e)
|
|
257
|
+
raise
|
|
258
|
+
ensure
|
|
259
|
+
AppOpticsAPM::API.log_exit(:redis, report_kvs)
|
|
260
|
+
end
|
|
261
|
+
else
|
|
262
|
+
call_pipeline_without_appoptics(pipeline)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if AppOpticsAPM::Config[:redis][:enabled]
|
|
271
|
+
if defined?(Redis) && Gem::Version.new(Redis::VERSION) >= Gem::Version.new('3.0.0')
|
|
272
|
+
AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting redis' if AppOpticsAPM::Config[:verbose]
|
|
273
|
+
AppOpticsAPM::Util.send_include(Redis::Client, AppOpticsAPM::Inst::Redis::Client)
|
|
274
|
+
end
|
|
275
|
+
end
|