solarwinds_apm 5.1.3 → 5.1.4

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