solarwinds_apm 5.1.3 → 5.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c274381dffd0448ab7bd8075035f550bd7374161af063c8b2eb53b1170b71dc3
4
- data.tar.gz: af7a66a8329b4c357a1368793d58b2be3e5c6db2a2d56f73287645f668e0cd9b
3
+ metadata.gz: 6b56699604e8448a606d69ed23ba957622c4b70d8d2cbcf070526105f2dcff87
4
+ data.tar.gz: 5b0f55643f2ea9072418c66c62a6f9ea4ca87fea74fbcec66574a341e0510c82
5
5
  SHA512:
6
- metadata.gz: d3ce5239fa623d41cb982578c51280caf4f1c7b7e152dfd97232f7e6fbc1d0faf0453ad30ba6e2e04fbc22a0f319f2c2569c0a1b53ce3f9a4e9f4924c16d8252
7
- data.tar.gz: 45b3d421e0acc5a90cdb9e61707d17e4739baf70d8ee916540b9e391c35ebd72c04b4493725358f94458c5ae0431e62f44fb5b087a08f86038bfd8b07d4ac398
6
+ metadata.gz: f77f2c0e8315832f92204bc0cdd29d6588f83cbf2751f8dfac2fb0402c65091c96238f0192da4473b99a0cf5197b44a66839f7d1c58fb5521bd11ce1a96defc6
7
+ data.tar.gz: 53c8e3740a5361fa4f9e403cf863f2044d821a58a7c42ac8708c859781c363b168af78e39da50b2ceeda783b752205cf0c0cb5bdf86afa7ffa4e63e80ae4dffe
data/.gitignore CHANGED
@@ -41,11 +41,18 @@ gemfiles/vendor*
41
41
  lib/libsolarwinds_apm.so
42
42
  vendor/
43
43
 
44
- # test
44
+ # test & test script
45
45
  tmp.rb
46
46
  apm.collector.st-ssp.solarwinds.com
47
47
  test/run_tests/.ruby_version_ubuntu
48
+ test/run_tests/.ruby_version_alpine
49
+ test/run_tests/.ruby_version_centos
50
+ test/run_tests/.ruby_version_ubuntu
51
+
52
+ redis-test.*
48
53
 
54
+ # compiling
55
+ ext/oboe_metal/verify
49
56
 
50
57
  # mac DS_Store
51
- .DS_Store
58
+ .DS_Store
data/CHANGELOG.md CHANGED
@@ -3,6 +3,20 @@ https://github.com/solarwindscloud/solarwinds-apm-ruby/releases
3
3
 
4
4
  Dates in this file are in the format MM/DD/YYYY.
5
5
 
6
+ # solarwinds_apm 5.1.4 (11/23/2022)
7
+
8
+ This release includes the following features:
9
+
10
+ * Update to the latest redis-rb gem (> 5.x)
11
+ * Update latest liboboe library (11.1.0)
12
+ * Start to support solarwinds-apm-ruby arm64/aarch64
13
+ * Init message update for swo/nh backends
14
+
15
+ Pushed to Rubygems:
16
+
17
+ https://rubygems.org/gems/solarwinds_apm/versions/5.1.4
18
+
19
+
6
20
  # solarwinds_apm 5.1.0 (09/15/2022)
7
21
 
8
22
  This release includes the following features:
data/README.md CHANGED
@@ -11,7 +11,8 @@ It requires an [Solarwinds] account to view metrics. Get yours,
11
11
 
12
12
  [![Gem Version](https://badge.fury.io/rb/solarwinds_apm.svg)](https://badge.fury.io/rb/solarwinds_apm)
13
13
 
14
- [![Run all Tests](https://github.com/appoptics/appoptics-apm-ruby/actions/workflows/run_tests.yml/badge.svg)](https://github.com/appoptics/appoptics-apm-ruby/actions/workflows/run_tests.yml)
14
+ [![Run all Tests](https://github.com/solarwindscloud/solarwinds-apm-ruby/actions/workflows/test_on_4_linux.yml/badge.svg)](https://github.com/solarwindscloud/solarwinds-apm-ruby/actions/workflows/test_on_4_linux.yml)
15
+
15
16
  [![C++ Tests](https://github.com/appoptics/appoptics-apm-ruby/actions/workflows/run_cpluplus_tests.yml/badge.svg)](https://github.com/appoptics/appoptics-apm-ruby/actions/workflows/run_cpluplus_tests.yml)
16
17
 
17
18
  [comment]: <> ([![Maintainability]&#40;https://api.codeclimate.com/v1/badges/ac7f36241a23a3a82fc5/maintainability&#41;]&#40;https://codeclimate.com/github/appoptics/appoptics-apm-ruby/maintainability&#41;)
@@ -363,7 +364,7 @@ To make this simpler, we've included a few rake tasks to automate this process:
363
364
 
364
365
  ```bash
365
366
  rake clean # make sure no old stuff is around
366
- rake fetch_oboe_file_from_staging # download c-files from staging
367
+ rake fetch_oboe_file["stg"] # download c-files from staging
367
368
  rake compile # Build the gem's c extension
368
369
  ```
369
370
 
@@ -381,3 +382,4 @@ See the README in the test directory.
381
382
  Copyright (c) 2018 SolarWinds, LLC
382
383
 
383
384
  Released under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0)
385
+
@@ -27,26 +27,39 @@ ao_include = File.join(ext_dir, 'src')
27
27
 
28
28
  # Download the appropriate liboboe from Staging or Production
29
29
  version = File.read(File.join(ao_include, 'VERSION')).strip
30
- if ENV['OBOE_STAGING'].to_s.downcase == 'true'
30
+ if ENV['OBOE_DEV'].to_s.downcase == 'true'
31
+ ao_path = "https://solarwinds-apm-staging.s3.us-west-2.amazonaws.com/apm/c-lib/nightly"
32
+ puts 'Fetching c-lib from DEVELOPMENT Build'
33
+ elsif ENV['OBOE_STAGING'].to_s.downcase == 'true'
31
34
  ao_path = File.join('https://agent-binaries.global.st-ssp.solarwinds.com/apm/c-lib/', version)
32
35
  puts 'Fetching c-lib from STAGING'
33
36
  else
34
37
  ao_path = File.join('https://agent-binaries.cloud.solarwinds.com/apm/c-lib/', version)
35
38
  end
36
39
 
37
- ao_arch = 'x86_64'
40
+ ao_arch = "x86_64"
41
+ system_arch = `uname -m` # for mac, the command is `uname` # "Darwin\n"; try `uname -a`
42
+ case system_arch.gsub("\n","")
43
+ when "x86_64"
44
+ ao_arch = "x86_64"
45
+ when "aarch64"
46
+ ao_arch = "aarch64"
47
+ end
48
+
38
49
  if File.exist?('/etc/alpine-release')
39
50
  version = File.read('/etc/alpine-release').strip
40
51
 
52
+ tmp_ao_arch = ao_arch.clone
41
53
  ao_arch =
42
54
  if Gem::Version.new(version) < Gem::Version.new('3.9')
43
- 'alpine-libressl-x86_64'
55
+ "alpine-libressl-#{tmp_ao_arch}"
44
56
  else # openssl
45
- 'alpine-x86_64'
57
+ "alpine-#{tmp_ao_arch}"
46
58
  end
47
59
  end
48
60
 
49
61
  ao_clib = "liboboe-1.0-#{ao_arch}.so.0.0.0"
62
+ ao_clib = "liboboe-1.0-#{ao_arch}.so" if ENV['OBOE_DEV'].to_s.downcase == 'true' # for dev build only
50
63
  ao_item = File.join(ao_path, ao_clib)
51
64
  ao_checksum_file = File.join(ao_lib_dir, "#{ao_clib}.sha256")
52
65
  clib = File.join(ao_lib_dir, ao_clib)
@@ -138,4 +151,4 @@ if success
138
151
  $stderr.puts '=================================================================='
139
152
  create_makefile('oboe_noop', 'noop')
140
153
  end
141
- end
154
+ end
@@ -0,0 +1 @@
1
+ ae491260c3b2dfbb2ada97de0c94e78424b2eb5bf19b2b752c25301057270c30
@@ -0,0 +1 @@
1
+ a1f5406931ce82b0297bbb1b2b22b151c544e47babc43f0116274f95002ce85e
@@ -1 +1 @@
1
- 08e53f05aebb831b3bb27410ad3163e193b4e3d7dbef3ac280e799f024623d95
1
+ ede3fcc827faba4d6cf09d2dd59a41325c462c9093994c2c2df202ae57416733
@@ -1 +1 @@
1
- 69ae267c2a31e29b1f518f9a30149e3bce1dd9a9e3a5a1c91c032f1b15ba3360
1
+ 079abfef35a869c3469908a62b97ad6f0f98f85a6f79e3c60265b251dc07dac3
@@ -1,2 +1,2 @@
1
- 11.0.0
1
+ 11.1.0
2
2
 
@@ -57,10 +57,9 @@ module SolarWindsAPM
57
57
  # KVOp (no KVKey)
58
58
 
59
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)
60
+ # call_pipelined is alias of call in redisclient middlewares
61
+ SolarWindsAPM::Util.method_alias(klass, :call, ::RedisClient)
62
+ SolarWindsAPM::Util.method_alias(klass, :call_pipelined, ::RedisClient)
64
63
  end
65
64
 
66
65
  # Given any Redis operation command array, this method
@@ -69,12 +68,14 @@ module SolarWindsAPM
69
68
  # @param command [Array] the Redis operation array
70
69
  # @param r [Return] the return value from the operation
71
70
  # @return [Hash] the Key/Values to report
72
- def extract_trace_details(command, r)
71
+ def extract_trace_details(command, r, config)
72
+ SolarWindsAPM.logger.debug "extract_trace_details command => #{command.inspect}"
73
73
  kvs = {}
74
74
  op = command.first
75
+ op = op.to_sym
75
76
 
76
77
  kvs[:KVOp] = command[0]
77
- kvs[:RemoteHost] = @options[:host]
78
+ kvs[:RemoteHost] = config.host
78
79
  unless NO_KEY_OPS.include?(op) || op == :del && command[1..-1].flatten.count > 1
79
80
  if command[1].is_a?(Array)
80
81
  kvs[:KVKey] = command[1].first
@@ -115,7 +116,7 @@ module SolarWindsAPM
115
116
  kvs[:KVHit] = r.nil? ? 0 : 1
116
117
 
117
118
  when :hdel, :hexists, :hget, :hset, :hsetnx
118
- kvs[:field] = command[2] unless command[2].is_a?(Array)
119
+ kvs[:field] = command[2] unless (command[2] && command[3]) # replace the idiom of command[2].is_a?(Array)
119
120
  if op == :hget
120
121
  kvs[:KVHit] = r.nil? ? 0 : 1
121
122
  end
@@ -136,7 +137,7 @@ module SolarWindsAPM
136
137
  else
137
138
  kvs[:Script] = command[2]
138
139
  end
139
- elsif command[1] == :exists
140
+ elsif command[1] == "exists"
140
141
  if command[2].is_a?(Array)
141
142
  kvs[:KVKey] = command[2].inspect
142
143
  else
@@ -166,6 +167,10 @@ module SolarWindsAPM
166
167
  end
167
168
  end # case op
168
169
  end # if KV_COLLECT_MAP[op]
170
+ # turn every string into number
171
+ # kvs.each do |k,v|
172
+ # kvs[k] = v.to_i if v.is_a(String)
173
+ # end
169
174
  rescue StandardError => e
170
175
  SolarWindsAPM.logger.debug "[solarwinds_apm/redis] Error collecting redis KVs: #{e.message}"
171
176
  SolarWindsAPM.logger.debug e.backtrace.join('\n')
@@ -178,16 +183,17 @@ module SolarWindsAPM
178
183
  #
179
184
  # @param pipeline [Redis::Pipeline] the Redis pipeline instance
180
185
  # @return [Hash] the Key/Values to report
181
- def extract_pipeline_details(pipeline)
186
+ def extract_pipeline_details(commands, config)
187
+ SolarWindsAPM.logger.debug "extract_pipeline_details command => #{commands.inspect}"
182
188
  kvs = {}
183
189
 
184
- kvs[:RemoteHost] = @options[:host]
190
+ kvs[:RemoteHost] = config.host
185
191
  kvs[:Backtrace] = SolarWindsAPM::API.backtrace if SolarWindsAPM::Config[:redis][:collect_backtraces]
186
192
 
187
- command_count = pipeline.commands.count
193
+ command_count = commands.count
188
194
  kvs[:KVOpCount] = command_count
189
195
 
190
- kvs[:KVOp] = if pipeline.commands.first == :multi
196
+ kvs[:KVOp] = if commands.first == :multi
191
197
  :multi
192
198
  else
193
199
  :pipeline
@@ -197,7 +203,7 @@ module SolarWindsAPM
197
203
  # of ops is reasonable
198
204
  if command_count < 12
199
205
  ops = []
200
- pipeline.commands.each do |c|
206
+ commands.each do |c|
201
207
  ops << c.first
202
208
  end
203
209
  kvs[:KVOps] = ops.join(', ')
@@ -210,17 +216,19 @@ module SolarWindsAPM
210
216
  end
211
217
 
212
218
  #
213
- # The wrapper method for Redis::Client.call. Here
219
+ # The wrapper method for RedisClient.call. Here
214
220
  # (when tracing) we capture KVs to report and pass
215
221
  # the call along
216
222
  #
217
- def call_with_sw_apm(command, &block)
223
+ def call_with_sw_apm(command, config, &block)
224
+ SolarWindsAPM.logger.debug "call_with_sw_apm command => #{command.inspect}"
218
225
  if SolarWindsAPM.tracing?
219
226
  SolarWindsAPM::API.log_entry(:redis, {})
220
227
 
221
228
  begin
222
- r = call_without_sw_apm(command, &block)
223
- report_kvs = extract_trace_details(command, r)
229
+ r = call_without_sw_apm(command, config, &block)
230
+ report_kvs = extract_trace_details(command, r, config)
231
+ SolarWindsAPM.logger.debug "call_with_sw_apm command => #{report_kvs.inspect}"
224
232
  r
225
233
  rescue StandardError => e
226
234
  SolarWindsAPM::API.log_exception(:redis, e)
@@ -230,26 +238,25 @@ module SolarWindsAPM
230
238
  end
231
239
 
232
240
  else
233
- call_without_sw_apm(command, &block)
241
+ call_without_sw_apm(command, config, &block)
234
242
  end
235
243
  end
236
244
 
237
245
  #
238
- # The wrapper method for Redis::Client.call_pipeline. Here
246
+ # The wrapper method for RedisClient.call_pipeline. Here
239
247
  # (when tracing) we capture KVs to report and pass the call along
240
- #
241
- def call_pipeline_with_sw_apm(pipeline)
248
+ # 5.0.0 + removed the deprecated pipelined and multi signature. Commands now MUST be called on the block argument, not the original redis instance
249
+ def call_pipelined_with_sw_apm(commands, config, &block)
250
+ SolarWindsAPM.logger.debug "call_pipelined_with_sw_apm command => #{commands.inspect}"
242
251
  if SolarWindsAPM.tracing?
243
252
  # Fall back to the raw tracing API so we can pass KVs
244
253
  # back on exit (a limitation of the SolarWindsAPM::API.trace
245
254
  # block method) This removes the need for an info
246
255
  # event to send additonal KVs
247
256
  SolarWindsAPM::API.log_entry(:redis, {})
248
-
249
- report_kvs = extract_pipeline_details(pipeline)
250
-
257
+ report_kvs = extract_pipeline_details(commands, config)
251
258
  begin
252
- call_pipeline_without_sw_apm(pipeline)
259
+ call_pipelined_without_sw_apm(commands, config, &block)
253
260
  rescue StandardError => e
254
261
  SolarWindsAPM::API.log_exception(:redis, e)
255
262
  raise
@@ -257,7 +264,7 @@ module SolarWindsAPM
257
264
  SolarWindsAPM::API.log_exit(:redis, report_kvs)
258
265
  end
259
266
  else
260
- call_pipeline_without_sw_apm(pipeline)
267
+ call_pipelined_without_sw_apm(commands, config, &block)
261
268
  end
262
269
  end
263
270
  end
@@ -266,8 +273,8 @@ module SolarWindsAPM
266
273
  end
267
274
 
268
275
  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)
276
+ if defined?(::RedisClient)
277
+ SolarWindsAPM.logger.info "[solarwinds_apm/loading] Instrumenting redis #{Redis::VERSION}" if SolarWindsAPM::Config[:verbose]
278
+ ::RedisClient.register(SolarWindsAPM::Inst::Redis::Client)
272
279
  end
273
280
  end
@@ -0,0 +1,273 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module SolarWindsAPM
5
+ module Inst
6
+ module RedisV4
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) && ::Redis::VERSION < '5' && ::Redis::VERSION > '3'
270
+ SolarWindsAPM.logger.info '[solarwinds_apm/loading] Instrumenting redis' if SolarWindsAPM::Config[:verbose]
271
+ SolarWindsAPM::Util.send_include(::Redis::Client, SolarWindsAPM::Inst::RedisV4::Client)
272
+ end
273
+ end