appoptics_apm 4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (226) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +43 -0
  3. data/.dockerignore +5 -0
  4. data/.gitignore +23 -0
  5. data/.rubocop.yml +5 -0
  6. data/.travis.yml +82 -0
  7. data/CHANGELOG.md +769 -0
  8. data/CONFIG.md +33 -0
  9. data/Dockerfile +41 -0
  10. data/Dockerfile_test +66 -0
  11. data/Gemfile +41 -0
  12. data/LICENSE +193 -0
  13. data/README.md +351 -0
  14. data/Rakefile +202 -0
  15. data/Vagrantfile +67 -0
  16. data/appoptics_apm.gemspec +55 -0
  17. data/build_gems.sh +15 -0
  18. data/docker-compose.yml +73 -0
  19. data/examples/DNT.md +35 -0
  20. data/examples/carrying_context.rb +220 -0
  21. data/examples/instrumenting_metal_controller.rb +8 -0
  22. data/examples/puma_on_heroku_config.rb +17 -0
  23. data/examples/tracing_async_threads.rb +124 -0
  24. data/examples/tracing_background_jobs.rb +53 -0
  25. data/examples/tracing_forked_processes.rb +99 -0
  26. data/examples/unicorn_on_heroku_config.rb +28 -0
  27. data/ext/oboe_metal/extconf.rb +54 -0
  28. data/ext/oboe_metal/lib/.keep +0 -0
  29. data/ext/oboe_metal/lib/liboboe-1.0.so.0.0.0 +0 -0
  30. data/ext/oboe_metal/noop/noop.c +7 -0
  31. data/ext/oboe_metal/src/VERSION +1 -0
  32. data/ext/oboe_metal/src/bson/bson.h +221 -0
  33. data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
  34. data/ext/oboe_metal/src/oboe.h +883 -0
  35. data/ext/oboe_metal/src/oboe.hpp +793 -0
  36. data/ext/oboe_metal/src/oboe_debug.h +50 -0
  37. data/ext/oboe_metal/src/oboe_wrap.cxx +6088 -0
  38. data/ext/oboe_metal/tests/test.rb +11 -0
  39. data/gemfiles/delayed_job.gemfile +36 -0
  40. data/gemfiles/frameworks.gemfile +44 -0
  41. data/gemfiles/instrumentation_mocked.gemfile +29 -0
  42. data/gemfiles/libraries.gemfile +85 -0
  43. data/gemfiles/rails23.gemfile +39 -0
  44. data/gemfiles/rails30.gemfile +42 -0
  45. data/gemfiles/rails31.gemfile +44 -0
  46. data/gemfiles/rails32.gemfile +54 -0
  47. data/gemfiles/rails40.gemfile +27 -0
  48. data/gemfiles/rails41.gemfile +27 -0
  49. data/gemfiles/rails42.gemfile +35 -0
  50. data/gemfiles/rails50.gemfile +44 -0
  51. data/gemfiles/rails51.gemfile +44 -0
  52. data/get_version.rb +5 -0
  53. data/init.rb +4 -0
  54. data/lib/appoptics_apm/api/layerinit.rb +39 -0
  55. data/lib/appoptics_apm/api/logging.rb +359 -0
  56. data/lib/appoptics_apm/api/memcache.rb +34 -0
  57. data/lib/appoptics_apm/api/profiling.rb +201 -0
  58. data/lib/appoptics_apm/api/tracing.rb +152 -0
  59. data/lib/appoptics_apm/api/util.rb +128 -0
  60. data/lib/appoptics_apm/api.rb +18 -0
  61. data/lib/appoptics_apm/base.rb +252 -0
  62. data/lib/appoptics_apm/config.rb +281 -0
  63. data/lib/appoptics_apm/frameworks/grape.rb +93 -0
  64. data/lib/appoptics_apm/frameworks/padrino/templates.rb +58 -0
  65. data/lib/appoptics_apm/frameworks/padrino.rb +52 -0
  66. data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +106 -0
  67. data/lib/appoptics_apm/frameworks/rails/inst/action_controller2.rb +61 -0
  68. data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +58 -0
  69. data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
  70. data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
  71. data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
  72. data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +58 -0
  73. data/lib/appoptics_apm/frameworks/rails/inst/action_view_2x.rb +56 -0
  74. data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +50 -0
  75. data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
  76. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
  77. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +28 -0
  78. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +30 -0
  79. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +120 -0
  80. data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +101 -0
  81. data/lib/appoptics_apm/frameworks/rails.rb +116 -0
  82. data/lib/appoptics_apm/frameworks/sinatra/templates.rb +56 -0
  83. data/lib/appoptics_apm/frameworks/sinatra.rb +71 -0
  84. data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
  85. data/lib/appoptics_apm/inst/bunny-consumer.rb +92 -0
  86. data/lib/appoptics_apm/inst/curb.rb +329 -0
  87. data/lib/appoptics_apm/inst/dalli.rb +85 -0
  88. data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
  89. data/lib/appoptics_apm/inst/em-http-request.rb +105 -0
  90. data/lib/appoptics_apm/inst/excon.rb +130 -0
  91. data/lib/appoptics_apm/inst/faraday.rb +77 -0
  92. data/lib/appoptics_apm/inst/http.rb +83 -0
  93. data/lib/appoptics_apm/inst/httpclient.rb +176 -0
  94. data/lib/appoptics_apm/inst/memcache.rb +102 -0
  95. data/lib/appoptics_apm/inst/memcached.rb +94 -0
  96. data/lib/appoptics_apm/inst/mongo.rb +242 -0
  97. data/lib/appoptics_apm/inst/mongo2.rb +225 -0
  98. data/lib/appoptics_apm/inst/moped.rb +466 -0
  99. data/lib/appoptics_apm/inst/rack.rb +146 -0
  100. data/lib/appoptics_apm/inst/redis.rb +275 -0
  101. data/lib/appoptics_apm/inst/resque.rb +151 -0
  102. data/lib/appoptics_apm/inst/rest-client.rb +50 -0
  103. data/lib/appoptics_apm/inst/sequel.rb +178 -0
  104. data/lib/appoptics_apm/inst/sidekiq-client.rb +53 -0
  105. data/lib/appoptics_apm/inst/sidekiq-worker.rb +67 -0
  106. data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
  107. data/lib/appoptics_apm/inst/typhoeus.rb +113 -0
  108. data/lib/appoptics_apm/instrumentation.rb +22 -0
  109. data/lib/appoptics_apm/legacy_method_profiling.rb +97 -0
  110. data/lib/appoptics_apm/loading.rb +66 -0
  111. data/lib/appoptics_apm/logger.rb +41 -0
  112. data/lib/appoptics_apm/method_profiling.rb +33 -0
  113. data/lib/appoptics_apm/ruby.rb +35 -0
  114. data/lib/appoptics_apm/support.rb +135 -0
  115. data/lib/appoptics_apm/test.rb +94 -0
  116. data/lib/appoptics_apm/thread_local.rb +26 -0
  117. data/lib/appoptics_apm/util.rb +312 -0
  118. data/lib/appoptics_apm/version.rb +15 -0
  119. data/lib/appoptics_apm/xtrace.rb +103 -0
  120. data/lib/appoptics_apm.rb +72 -0
  121. data/lib/joboe_metal.rb +214 -0
  122. data/lib/oboe/README +2 -0
  123. data/lib/oboe/backward_compatibility.rb +80 -0
  124. data/lib/oboe/inst/rack.rb +11 -0
  125. data/lib/oboe.rb +7 -0
  126. data/lib/oboe_metal.rb +187 -0
  127. data/lib/rails/generators/appoptics_apm/install_generator.rb +45 -0
  128. data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +222 -0
  129. data/ruby_setup.sh +47 -0
  130. data/run_docker_build_gem_upload_to_packagecloud.sh +20 -0
  131. data/run_tests_docker.rb +32 -0
  132. data/test/benchmark/README.md +65 -0
  133. data/test/benchmark/logging_bench.rb +54 -0
  134. data/test/benchmark/with_libraries_gemfile/bunny_bench.rb +69 -0
  135. data/test/benchmark/with_rails5x_gemfile/action_controller5x_bench.rb +43 -0
  136. data/test/frameworks/apps/grape_nested.rb +33 -0
  137. data/test/frameworks/apps/grape_simple.rb +80 -0
  138. data/test/frameworks/apps/padrino_simple.rb +80 -0
  139. data/test/frameworks/apps/sinatra_simple.rb +55 -0
  140. data/test/frameworks/grape_test.rb +286 -0
  141. data/test/frameworks/padrino_test.rb +222 -0
  142. data/test/frameworks/rails3x_test.rb +554 -0
  143. data/test/frameworks/rails4x_test.rb +570 -0
  144. data/test/frameworks/rails5x_api_test.rb +210 -0
  145. data/test/frameworks/rails5x_test.rb +376 -0
  146. data/test/frameworks/rails_shared_tests.rb +172 -0
  147. data/test/frameworks/sinatra_test.rb +140 -0
  148. data/test/instrumentation/bunny_client_test.rb +276 -0
  149. data/test/instrumentation/bunny_consumer_test.rb +204 -0
  150. data/test/instrumentation/curb_test.rb +398 -0
  151. data/test/instrumentation/dalli_test.rb +177 -0
  152. data/test/instrumentation/em_http_request_test.rb +89 -0
  153. data/test/instrumentation/excon_test.rb +231 -0
  154. data/test/instrumentation/faraday_test.rb +228 -0
  155. data/test/instrumentation/http_test.rb +143 -0
  156. data/test/instrumentation/httpclient_test.rb +320 -0
  157. data/test/instrumentation/memcache_test.rb +260 -0
  158. data/test/instrumentation/memcached_test.rb +229 -0
  159. data/test/instrumentation/mongo_v1_test.rb +479 -0
  160. data/test/instrumentation/mongo_v2_index_test.rb +124 -0
  161. data/test/instrumentation/mongo_v2_test.rb +584 -0
  162. data/test/instrumentation/mongo_v2_view_test.rb +435 -0
  163. data/test/instrumentation/moped_test.rb +517 -0
  164. data/test/instrumentation/rack_test.rb +165 -0
  165. data/test/instrumentation/redis_hashes_test.rb +268 -0
  166. data/test/instrumentation/redis_keys_test.rb +321 -0
  167. data/test/instrumentation/redis_lists_test.rb +310 -0
  168. data/test/instrumentation/redis_misc_test.rb +163 -0
  169. data/test/instrumentation/redis_sets_test.rb +296 -0
  170. data/test/instrumentation/redis_sortedsets_test.rb +328 -0
  171. data/test/instrumentation/redis_strings_test.rb +349 -0
  172. data/test/instrumentation/resque_test.rb +185 -0
  173. data/test/instrumentation/rest-client_test.rb +288 -0
  174. data/test/instrumentation/sequel_mysql2_test.rb +353 -0
  175. data/test/instrumentation/sequel_mysql_test.rb +334 -0
  176. data/test/instrumentation/sequel_pg_test.rb +336 -0
  177. data/test/instrumentation/sidekiq-client_test.rb +159 -0
  178. data/test/instrumentation/sidekiq-worker_test.rb +180 -0
  179. data/test/instrumentation/twitter-cassandra_test.rb +424 -0
  180. data/test/instrumentation/typhoeus_test.rb +284 -0
  181. data/test/jobs/delayed_job/db_worker_job.rb +29 -0
  182. data/test/jobs/delayed_job/error_worker_job.rb +10 -0
  183. data/test/jobs/delayed_job/remote_call_worker_job.rb +20 -0
  184. data/test/jobs/resque/db_worker_job.rb +29 -0
  185. data/test/jobs/resque/error_worker_job.rb +10 -0
  186. data/test/jobs/resque/remote_call_worker_job.rb +20 -0
  187. data/test/jobs/sidekiq/db_worker_job.rb +29 -0
  188. data/test/jobs/sidekiq/error_worker_job.rb +10 -0
  189. data/test/jobs/sidekiq/remote_call_worker_job.rb +20 -0
  190. data/test/minitest_helper.rb +276 -0
  191. data/test/mocked/curb_mocked_test.rb +311 -0
  192. data/test/mocked/excon_mocked_test.rb +166 -0
  193. data/test/mocked/faraday_mocked_test.rb +93 -0
  194. data/test/mocked/http_mocked_test.rb +129 -0
  195. data/test/mocked/httpclient_mocked_test.rb +245 -0
  196. data/test/mocked/rest_client_mocked_test.rb +103 -0
  197. data/test/mocked/typhoeus_mocked_test.rb +192 -0
  198. data/test/models/widget.rb +36 -0
  199. data/test/profiling/legacy_method_profiling_test.rb +201 -0
  200. data/test/profiling/method_profiling_test.rb +631 -0
  201. data/test/queues/delayed_job-client_test.rb +95 -0
  202. data/test/queues/delayed_job-worker_test.rb +91 -0
  203. data/test/reporter/reporter_test.rb +14 -0
  204. data/test/servers/delayed_job.rb +107 -0
  205. data/test/servers/rackapp_8101.rb +29 -0
  206. data/test/servers/rails3x_8140.rb +96 -0
  207. data/test/servers/rails4x_8140.rb +96 -0
  208. data/test/servers/rails5x_8140.rb +95 -0
  209. data/test/servers/rails5x_api_8150.rb +78 -0
  210. data/test/servers/sidekiq.rb +29 -0
  211. data/test/servers/sidekiq.yml +7 -0
  212. data/test/servers/sidekiq_initializer.rb +25 -0
  213. data/test/settings +0 -0
  214. data/test/support/auto_tracing_test.rb +50 -0
  215. data/test/support/backcompat_test.rb +276 -0
  216. data/test/support/config_test.rb +149 -0
  217. data/test/support/dnt_test.rb +98 -0
  218. data/test/support/init_report_test.rb +25 -0
  219. data/test/support/liboboe_settings_test.rb +110 -0
  220. data/test/support/logging_test.rb +130 -0
  221. data/test/support/noop_test.rb +88 -0
  222. data/test/support/sql_sanitize_test.rb +55 -0
  223. data/test/support/tracing_mode_test.rb +33 -0
  224. data/test/support/tvalias_test.rb +15 -0
  225. data/test/support/xtrace_test.rb +41 -0
  226. metadata +475 -0
@@ -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 = [: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 "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
@@ -0,0 +1,151 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ require 'socket'
5
+ require 'json'
6
+
7
+ module AppOpticsAPM
8
+ module Inst
9
+ module ResqueClient
10
+ def self.included(klass)
11
+ klass.send :extend, ::Resque
12
+ ::AppOpticsAPM::Util.method_alias(klass, :enqueue, ::Resque)
13
+ ::AppOpticsAPM::Util.method_alias(klass, :enqueue_to, ::Resque)
14
+ ::AppOpticsAPM::Util.method_alias(klass, :dequeue, ::Resque)
15
+ end
16
+
17
+ def extract_trace_details(op, klass, args)
18
+ report_kvs = {}
19
+
20
+ begin
21
+ report_kvs[:Spec] = :pushq
22
+ report_kvs[:Flavor] = :resque
23
+ report_kvs[:JobName] = klass.to_s
24
+
25
+ if AppOpticsAPM::Config[:resqueclient][:log_args]
26
+ kv_args = args.to_json
27
+
28
+ # Limit the argument json string to 1024 bytes
29
+ if kv_args.length > 1024
30
+ report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
31
+ else
32
+ report_kvs[:Args] = kv_args
33
+ end
34
+ end
35
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:resqueclient][:collect_backtraces]
36
+ report_kvs[:Queue] = klass.instance_variable_get(:@queue)
37
+ rescue => e
38
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if AppOpticsAPM::Config[:verbose]
39
+ end
40
+
41
+ report_kvs
42
+ end
43
+
44
+ def enqueue_with_appoptics(klass, *args)
45
+ if AppOpticsAPM.tracing?
46
+ report_kvs = extract_trace_details(:enqueue, klass, args)
47
+
48
+ AppOpticsAPM::API.trace(:'resque-client', report_kvs, :enqueue) do
49
+ enqueue_without_appoptics(klass, *args)
50
+ end
51
+ else
52
+ enqueue_without_appoptics(klass, *args)
53
+ end
54
+ end
55
+
56
+ def enqueue_to_with_appoptics(queue, klass, *args)
57
+ if AppOpticsAPM.tracing? && !AppOpticsAPM.tracing_layer_op?(:enqueue)
58
+ report_kvs = extract_trace_details(:enqueue_to, klass, args)
59
+ report_kvs[:Queue] = queue.to_s if queue
60
+
61
+ AppOpticsAPM::API.trace(:'resque-client', report_kvs) do
62
+ enqueue_to_without_appoptics(queue, klass, *args)
63
+ end
64
+ else
65
+ enqueue_to_without_appoptics(queue, klass, *args)
66
+ end
67
+ end
68
+
69
+ def dequeue_with_appoptics(klass, *args)
70
+ if AppOpticsAPM.tracing?
71
+ report_kvs = extract_trace_details(:dequeue, klass, args)
72
+
73
+ AppOpticsAPM::API.trace(:'resque-client', report_kvs) do
74
+ dequeue_without_appoptics(klass, *args)
75
+ end
76
+ else
77
+ dequeue_without_appoptics(klass, *args)
78
+ end
79
+ end
80
+ end
81
+
82
+ module ResqueWorker
83
+ def self.included(klass)
84
+ ::AppOpticsAPM::Util.method_alias(klass, :perform, ::Resque::Worker)
85
+ end
86
+
87
+ def perform_with_appoptics(job)
88
+ report_kvs = {}
89
+
90
+ begin
91
+ report_kvs[:Spec] = :job
92
+ report_kvs[:Flavor] = :resque
93
+ report_kvs[:JobName] = job.payload['class'].to_s
94
+ report_kvs[:Queue] = job.queue
95
+
96
+ # Set these keys for the ability to separate out
97
+ # background tasks into a separate app on the server-side UI
98
+
99
+ report_kvs[:'HTTP-Host'] = Socket.gethostname
100
+ report_kvs[:Controller] = "Resque_#{job.queue}"
101
+ report_kvs[:Action] = job.payload['class'].to_s
102
+ report_kvs[:URL] = "/resque/#{job.queue}/#{job.payload['class']}"
103
+
104
+ if AppOpticsAPM::Config[:resqueworker][:log_args]
105
+ kv_args = job.payload['args'].to_json
106
+
107
+ # Limit the argument json string to 1024 bytes
108
+ if kv_args.length > 1024
109
+ report_kvs[:Args] = kv_args[0..1023] + '...[snipped]'
110
+ else
111
+ report_kvs[:Args] = kv_args
112
+ end
113
+ end
114
+
115
+ report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:resqueworker][:collect_backtraces]
116
+ rescue => e
117
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" if AppOpticsAPM::Config[:verbose]
118
+ end
119
+
120
+ AppOpticsAPM::API.start_trace(:'resque-worker', nil, report_kvs) do
121
+ perform_without_appoptics(job)
122
+ end
123
+ end
124
+ end
125
+
126
+ module ResqueJob
127
+ def self.included(klass)
128
+ ::AppOpticsAPM::Util.method_alias(klass, :fail, ::Resque::Job)
129
+ end
130
+
131
+ def fail_with_appoptics(exception)
132
+ if AppOpticsAPM.tracing?
133
+ AppOpticsAPM::API.log_exception(:resque, exception)
134
+ end
135
+ fail_without_appoptics(exception)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ if defined?(::Resque) && RUBY_VERSION >= '1.9.3'
142
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting resque' if AppOpticsAPM::Config[:verbose]
143
+
144
+ ::AppOpticsAPM::Util.send_include(::Resque, ::AppOpticsAPM::Inst::ResqueClient) if AppOpticsAPM::Config[:resqueclient][:enabled]
145
+ ::AppOpticsAPM::Util.send_include(::Resque::Worker, ::AppOpticsAPM::Inst::ResqueWorker) if AppOpticsAPM::Config[:resqueworker][:enabled]
146
+ if AppOpticsAPM::Config[:resqueclient][:enabled] || AppOpticsAPM::Config[:resqueworker][:enabled]
147
+ ::AppOpticsAPM::Util.send_include(::Resque::Job, ::AppOpticsAPM::Inst::ResqueJob)
148
+ end
149
+ end
150
+
151
+
@@ -0,0 +1,50 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ module RestClientRequest
7
+ def self.included(klass)
8
+ ::AppOpticsAPM::Util.method_alias(klass, :execute, ::RestClient::Request)
9
+ end
10
+
11
+ ##
12
+ # execute_with_appoptics
13
+ #
14
+ # The wrapper method for RestClient::Request.execute
15
+ #
16
+ def execute_with_appoptics(&block)
17
+ blacklisted = AppOpticsAPM::API.blacklisted?(uri)
18
+
19
+ unless AppOpticsAPM.tracing?
20
+ xtrace = AppOpticsAPM::Context.toString
21
+ @processed_headers['X-Trace'] = AppOpticsAPM::Context.toString if AppOpticsAPM::XTrace.valid?(xtrace) && !blacklisted
22
+ return execute_without_appoptics(&block)
23
+ end
24
+
25
+ begin
26
+ kvs = {}
27
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:rest_client][:collect_backtraces]
28
+ AppOpticsAPM::API.log_entry('rest-client', kvs)
29
+
30
+ @processed_headers['X-Trace'] = AppOpticsAPM::Context.toString unless blacklisted
31
+
32
+ # The core rest-client call
33
+ execute_without_appoptics(&block)
34
+ rescue => e
35
+ AppOpticsAPM::API.log_exception('rest-client', e)
36
+ raise e
37
+ ensure
38
+ AppOpticsAPM::API.log_exit('rest-client')
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ if AppOpticsAPM::Config[:rest_client][:enabled]
46
+ if defined?(::RestClient)
47
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting rest-client' if AppOpticsAPM::Config[:verbose]
48
+ ::AppOpticsAPM::Util.send_include(::RestClient::Request, ::AppOpticsAPM::Inst::RestClientRequest)
49
+ end
50
+ end
@@ -0,0 +1,178 @@
1
+ # Copyright (c) 2016 SolarWinds, LLC.
2
+ # All rights reserved.
3
+
4
+ module AppOpticsAPM
5
+ module Inst
6
+ ##
7
+ # AppOpticsAPM::Inst::Sequel
8
+ #
9
+ # The common (shared) methods used by the AppOpticsAPM Sequel instrumentation
10
+ # across multiple modules/classes.
11
+ #
12
+ module Sequel
13
+ ##
14
+ # extract_trace_details
15
+ #
16
+ # Given SQL and the options hash, this method extracts the interesting
17
+ # bits for reporting to the AppOptics dashboard.
18
+ #
19
+ def extract_trace_details(sql, opts)
20
+ kvs = {}
21
+
22
+ if !sql.is_a?(String)
23
+ kvs[:IsPreparedStatement] = true
24
+ end
25
+
26
+ if ::Sequel::VERSION > '4.36.0' && !sql.is_a?(String)
27
+ # In 4.37.0, sql was converted to a prepared statement object
28
+ sql = sql.prepared_sql unless sql.is_a?(Symbol)
29
+ end
30
+
31
+ if AppOpticsAPM::Config[:sanitize_sql]
32
+ # Sanitize SQL and don't report binds
33
+ if sql.is_a?(Symbol)
34
+ kvs[:Query] = sql
35
+ else
36
+ kvs[:Query] = AppOpticsAPM::Util.sanitize_sql(sql)
37
+ end
38
+ else
39
+ # Report raw SQL and any binds if they exist
40
+ kvs[:Query] = sql.to_s
41
+ kvs[:QueryArgs] = opts[:arguments] if opts.is_a?(Hash) && opts.key?(:arguments)
42
+ end
43
+
44
+ kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:sequel][:collect_backtraces]
45
+
46
+ if ::Sequel::VERSION < '3.41.0' && !(self.class.to_s =~ /Dataset$/)
47
+ db_opts = @opts
48
+ elsif @pool
49
+ db_opts = @pool.db.opts
50
+ else
51
+ db_opts = @db.opts
52
+ end
53
+
54
+ kvs[:Database] = db_opts[:database]
55
+ kvs[:RemoteHost] = db_opts[:host]
56
+ kvs[:RemotePort] = db_opts[:port] if db_opts.key?(:port)
57
+ kvs[:Flavor] = db_opts[:adapter]
58
+ rescue => e
59
+ AppOpticsAPM.logger.debug "[appoptics_apm/debug Error capturing Sequel KVs: #{e.message}" if AppOpticsAPM::Config[:verbose]
60
+ ensure
61
+ return kvs
62
+ end
63
+
64
+ ##
65
+ # exec_with_appoptics
66
+ #
67
+ # This method wraps and routes the call to the specified
68
+ # original method call
69
+ #
70
+ def exec_with_appoptics(method, sql, opts = ::Sequel::OPTS, &block)
71
+ if AppOpticsAPM.tracing?
72
+ kvs = extract_trace_details(sql, opts)
73
+ AppOpticsAPM::API.log_entry(:sequel, kvs)
74
+ end
75
+
76
+ send(method, sql, opts, &block)
77
+ rescue => e
78
+ AppOpticsAPM::API.log_exception(:sequel, e)
79
+ raise e
80
+ ensure
81
+ AppOpticsAPM::API.log_exit(:sequel)
82
+ end
83
+ end
84
+
85
+ module SequelDatabase
86
+ include AppOpticsAPM::Inst::Sequel
87
+
88
+ def self.included(klass)
89
+ ::AppOpticsAPM::Util.method_alias(klass, :run, ::Sequel::Database)
90
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_ddl, ::Sequel::Database)
91
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_dui, ::Sequel::Database)
92
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_insert, ::Sequel::Database)
93
+ end
94
+
95
+ def run_with_appoptics(sql, opts = ::Sequel::OPTS)
96
+ if AppOpticsAPM.tracing?
97
+ kvs = extract_trace_details(sql, opts)
98
+ AppOpticsAPM::API.log_entry(:sequel, kvs)
99
+ end
100
+
101
+ run_without_appoptics(sql, opts)
102
+ rescue => e
103
+ AppOpticsAPM::API.log_exception(:sequel, e)
104
+ raise e
105
+ ensure
106
+ AppOpticsAPM::API.log_exit(:sequel)
107
+ end
108
+
109
+ def execute_ddl_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
110
+ # If we're already tracing a sequel operation, then this call likely came
111
+ # from Sequel::Dataset. In this case, just pass it on.
112
+ return execute_ddl_without_appoptics(sql, opts, &block) if AppOpticsAPM.tracing_layer?(:sequel)
113
+
114
+ exec_with_appoptics(:execute_ddl_without_appoptics, sql, opts, &block)
115
+ end
116
+
117
+ def execute_dui_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
118
+ # If we're already tracing a sequel operation, then this call likely came
119
+ # from Sequel::Dataset. In this case, just pass it on.
120
+ return execute_dui_without_appoptics(sql, opts, &block) if AppOpticsAPM.tracing_layer?(:sequel)
121
+
122
+ exec_with_appoptics(:execute_dui_without_appoptics, sql, opts, &block)
123
+ end
124
+
125
+ def execute_insert_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
126
+ # If we're already tracing a sequel operation, then this call likely came
127
+ # from Sequel::Dataset. In this case, just pass it on.
128
+ return execute_insert_without_appoptics(sql, opts, &block) if AppOpticsAPM.tracing_layer?(:sequel)
129
+
130
+ exec_with_appoptics(:execute_insert_without_appoptics, sql, opts, &block)
131
+ end
132
+ end # module SequelDatabase
133
+
134
+ module SequelDataset
135
+ include AppOpticsAPM::Inst::Sequel
136
+
137
+ def self.included(klass)
138
+ ::AppOpticsAPM::Util.method_alias(klass, :execute, ::Sequel::Dataset)
139
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_ddl, ::Sequel::Dataset)
140
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_dui, ::Sequel::Dataset)
141
+ ::AppOpticsAPM::Util.method_alias(klass, :execute_insert, ::Sequel::Dataset)
142
+ end
143
+
144
+ def execute_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
145
+ exec_with_appoptics(:execute_without_appoptics, sql, opts, &block)
146
+ end
147
+
148
+ def execute_ddl_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
149
+ exec_with_appoptics(:execute_ddl_without_appoptics, sql, opts, &block)
150
+ end
151
+
152
+ def execute_dui_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
153
+ exec_with_appoptics(:execute_dui_without_appoptics, sql, opts, &block)
154
+ end
155
+
156
+ def execute_insert_with_appoptics(sql, opts = ::Sequel::OPTS, &block)
157
+ exec_with_appoptics(:execute_insert_without_appoptics, sql, opts, &block)
158
+ end
159
+
160
+ end # module SequelDataset
161
+ end # module Inst
162
+ end # module AppOpticsAPM
163
+
164
+ if AppOpticsAPM::Config[:sequel][:enabled]
165
+ if defined?(::Sequel) && ::Sequel::VERSION < '4.0.0'
166
+ # For versions before 4.0.0, Sequel::OPTS wasn't defined.
167
+ # Define it as an empty hash for backwards compatibility.
168
+ module ::Sequel
169
+ OPTS = {}
170
+ end
171
+ end
172
+
173
+ if defined?(::Sequel)
174
+ AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting sequel' if AppOpticsAPM::Config[:verbose]
175
+ ::AppOpticsAPM::Util.send_include(::Sequel::Database, ::AppOpticsAPM::Inst::SequelDatabase)
176
+ ::AppOpticsAPM::Util.send_include(::Sequel::Dataset, ::AppOpticsAPM::Inst::SequelDataset)
177
+ end
178
+ end