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.
Files changed (104) 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/.gitignore +29 -0
  5. data/.rubocop.yml +8 -0
  6. data/.travis.yml +121 -0
  7. data/.yardopts +4 -0
  8. data/CHANGELOG.md +769 -0
  9. data/CONFIG.md +33 -0
  10. data/Gemfile +29 -0
  11. data/LICENSE +193 -0
  12. data/README.md +393 -0
  13. data/Rakefile +230 -0
  14. data/appoptics_apm.gemspec +61 -0
  15. data/bin/appoptics_apm_config +15 -0
  16. data/build_gem.sh +15 -0
  17. data/build_gem_upload_to_packagecloud.sh +20 -0
  18. data/examples/SDK/01_basic_tracing.rb +67 -0
  19. data/examples/carrying_context.rb +220 -0
  20. data/ext/oboe_metal/extconf.rb +114 -0
  21. data/ext/oboe_metal/lib/.keep +0 -0
  22. data/ext/oboe_metal/noop/noop.c +7 -0
  23. data/ext/oboe_metal/src/VERSION +1 -0
  24. data/init.rb +4 -0
  25. data/lib/appoptics_apm.rb +76 -0
  26. data/lib/appoptics_apm/api.rb +20 -0
  27. data/lib/appoptics_apm/api/layerinit.rb +41 -0
  28. data/lib/appoptics_apm/api/logging.rb +375 -0
  29. data/lib/appoptics_apm/api/memcache.rb +37 -0
  30. data/lib/appoptics_apm/api/metrics.rb +55 -0
  31. data/lib/appoptics_apm/api/profiling.rb +203 -0
  32. data/lib/appoptics_apm/api/tracing.rb +53 -0
  33. data/lib/appoptics_apm/api/util.rb +122 -0
  34. data/lib/appoptics_apm/base.rb +230 -0
  35. data/lib/appoptics_apm/config.rb +254 -0
  36. data/lib/appoptics_apm/frameworks/grape.rb +97 -0
  37. data/lib/appoptics_apm/frameworks/padrino.rb +108 -0
  38. data/lib/appoptics_apm/frameworks/rails.rb +94 -0
  39. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +104 -0
  40. data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +55 -0
  41. data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
  42. data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  43. data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  44. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +58 -0
  45. data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +50 -0
  46. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
  47. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
  48. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +29 -0
  49. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +31 -0
  50. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +119 -0
  51. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +108 -0
  52. data/lib/appoptics_apm/frameworks/sinatra.rb +125 -0
  53. data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
  54. data/lib/appoptics_apm/inst/bunny-consumer.rb +89 -0
  55. data/lib/appoptics_apm/inst/curb.rb +330 -0
  56. data/lib/appoptics_apm/inst/dalli.rb +85 -0
  57. data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
  58. data/lib/appoptics_apm/inst/em-http-request.rb +101 -0
  59. data/lib/appoptics_apm/inst/excon.rb +125 -0
  60. data/lib/appoptics_apm/inst/faraday.rb +94 -0
  61. data/lib/appoptics_apm/inst/grpc_client.rb +162 -0
  62. data/lib/appoptics_apm/inst/grpc_server.rb +120 -0
  63. data/lib/appoptics_apm/inst/http.rb +73 -0
  64. data/lib/appoptics_apm/inst/httpclient.rb +174 -0
  65. data/lib/appoptics_apm/inst/memcached.rb +86 -0
  66. data/lib/appoptics_apm/inst/mongo.rb +246 -0
  67. data/lib/appoptics_apm/inst/mongo2.rb +225 -0
  68. data/lib/appoptics_apm/inst/moped.rb +466 -0
  69. data/lib/appoptics_apm/inst/rack.rb +199 -0
  70. data/lib/appoptics_apm/inst/redis.rb +275 -0
  71. data/lib/appoptics_apm/inst/resque.rb +151 -0
  72. data/lib/appoptics_apm/inst/rest-client.rb +48 -0
  73. data/lib/appoptics_apm/inst/sequel.rb +178 -0
  74. data/lib/appoptics_apm/inst/sidekiq-client.rb +55 -0
  75. data/lib/appoptics_apm/inst/sidekiq-worker.rb +65 -0
  76. data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
  77. data/lib/appoptics_apm/inst/typhoeus.rb +108 -0
  78. data/lib/appoptics_apm/instrumentation.rb +22 -0
  79. data/lib/appoptics_apm/legacy_method_profiling.rb +90 -0
  80. data/lib/appoptics_apm/loading.rb +65 -0
  81. data/lib/appoptics_apm/logger.rb +42 -0
  82. data/lib/appoptics_apm/method_profiling.rb +33 -0
  83. data/lib/appoptics_apm/noop/README.md +9 -0
  84. data/lib/appoptics_apm/noop/context.rb +26 -0
  85. data/lib/appoptics_apm/noop/metadata.rb +22 -0
  86. data/lib/appoptics_apm/ruby.rb +35 -0
  87. data/lib/appoptics_apm/sdk/custom_metrics.rb +92 -0
  88. data/lib/appoptics_apm/sdk/tracing.rb +315 -0
  89. data/lib/appoptics_apm/support.rb +119 -0
  90. data/lib/appoptics_apm/test.rb +94 -0
  91. data/lib/appoptics_apm/thread_local.rb +26 -0
  92. data/lib/appoptics_apm/util.rb +319 -0
  93. data/lib/appoptics_apm/version.rb +15 -0
  94. data/lib/appoptics_apm/xtrace.rb +103 -0
  95. data/lib/joboe_metal.rb +212 -0
  96. data/lib/oboe.rb +7 -0
  97. data/lib/oboe/README +2 -0
  98. data/lib/oboe/backward_compatibility.rb +80 -0
  99. data/lib/oboe/inst/rack.rb +11 -0
  100. data/lib/oboe_metal.rb +198 -0
  101. data/lib/rails/generators/appoptics_apm/install_generator.rb +45 -0
  102. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +265 -0
  103. data/yardoc_frontpage.md +26 -0
  104. 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