oneapm_rpm 1.1.0

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 (234) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rubocop.yml +725 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +1 -0
  7. data/README.md +3 -0
  8. data/config/cert/cacert.pem +1177 -0
  9. data/config/database.yml +5 -0
  10. data/lib/initializers/goliath.rb +11 -0
  11. data/lib/initializers/other.rb +1 -0
  12. data/lib/initializers/rails.rb +15 -0
  13. data/lib/one_apm/agent.rb +253 -0
  14. data/lib/one_apm/agent/agent.rb +283 -0
  15. data/lib/one_apm/agent/agent/connect.rb +175 -0
  16. data/lib/one_apm/agent/agent/container_data_manager.rb +218 -0
  17. data/lib/one_apm/agent/agent/forkable_dispatcher_functions.rb +96 -0
  18. data/lib/one_apm/agent/agent/helpers.rb +45 -0
  19. data/lib/one_apm/agent/agent/start.rb +226 -0
  20. data/lib/one_apm/agent/agent/start_worker_thread.rb +148 -0
  21. data/lib/one_apm/agent/busy_calculator.rb +115 -0
  22. data/lib/one_apm/agent/cross_app/cross_app_monitor.rb +181 -0
  23. data/lib/one_apm/agent/cross_app/cross_app_tracing.rb +336 -0
  24. data/lib/one_apm/agent/database.rb +308 -0
  25. data/lib/one_apm/agent/database/active_record_helper.rb +80 -0
  26. data/lib/one_apm/agent/database/obfuscation_helpers.rb +76 -0
  27. data/lib/one_apm/agent/database/obfuscator.rb +78 -0
  28. data/lib/one_apm/agent/database/postgres_explain_obfuscator.rb +45 -0
  29. data/lib/one_apm/agent/datastores.rb +175 -0
  30. data/lib/one_apm/agent/datastores/metric_helper.rb +83 -0
  31. data/lib/one_apm/agent/datastores/mongo.rb +27 -0
  32. data/lib/one_apm/agent/datastores/mongo/metric_translator.rb +189 -0
  33. data/lib/one_apm/agent/datastores/mongo/obfuscator.rb +37 -0
  34. data/lib/one_apm/agent/datastores/mongo/statement_formatter.rb +51 -0
  35. data/lib/one_apm/agent/event/event_listener.rb +40 -0
  36. data/lib/one_apm/agent/event/event_loop.rb +191 -0
  37. data/lib/one_apm/agent/event/worker_loop.rb +97 -0
  38. data/lib/one_apm/agent/harvester.rb +48 -0
  39. data/lib/one_apm/agent/inbound_request_monitor.rb +30 -0
  40. data/lib/one_apm/agent/javascript_instrumentor.rb +186 -0
  41. data/lib/one_apm/agent/pipe/pipe_channel_manager.rb +275 -0
  42. data/lib/one_apm/agent/pipe/pipe_service.rb +81 -0
  43. data/lib/one_apm/agent/sampler.rb +55 -0
  44. data/lib/one_apm/agent/sampler_collection.rb +65 -0
  45. data/lib/one_apm/agent/samplers/cpu_sampler.rb +49 -0
  46. data/lib/one_apm/agent/samplers/delayed_job_sampler.rb +109 -0
  47. data/lib/one_apm/agent/samplers/memory_sampler.rb +144 -0
  48. data/lib/one_apm/agent/samplers/object_sampler.rb +22 -0
  49. data/lib/one_apm/agent/samplers/vm_sampler.rb +124 -0
  50. data/lib/one_apm/agent/synthetics_monitor.rb +48 -0
  51. data/lib/one_apm/agent/threading/agent_thread.rb +74 -0
  52. data/lib/one_apm/agent/threading/backtrace_node.rb +133 -0
  53. data/lib/one_apm/agent/threading/backtrace_service.rb +259 -0
  54. data/lib/one_apm/agent/threading/thread_profile.rb +155 -0
  55. data/lib/one_apm/collector/collector/helper.rb +139 -0
  56. data/lib/one_apm/collector/collector/http_connection.rb +254 -0
  57. data/lib/one_apm/collector/collector/server_methods.rb +71 -0
  58. data/lib/one_apm/collector/collector_service.rb +123 -0
  59. data/lib/one_apm/collector/commands/agent_command.rb +17 -0
  60. data/lib/one_apm/collector/commands/thread_profiler_session.rb +108 -0
  61. data/lib/one_apm/collector/commands/xray_session.rb +53 -0
  62. data/lib/one_apm/collector/commands/xray_session_collection.rb +156 -0
  63. data/lib/one_apm/collector/containers/agent_command_router.rb +153 -0
  64. data/lib/one_apm/collector/containers/custom_event_aggregator.rb +94 -0
  65. data/lib/one_apm/collector/containers/error_collector.rb +349 -0
  66. data/lib/one_apm/collector/containers/sql_sampler.rb +331 -0
  67. data/lib/one_apm/collector/containers/stats_engine.rb +34 -0
  68. data/lib/one_apm/collector/containers/transaction_event_aggregator.rb +249 -0
  69. data/lib/one_apm/collector/containers/transaction_sampler.rb +352 -0
  70. data/lib/one_apm/collector/containers/utilization_data.rb +36 -0
  71. data/lib/one_apm/collector/stats_engine/gc_profiler.rb +106 -0
  72. data/lib/one_apm/collector/stats_engine/metric_stats.rb +243 -0
  73. data/lib/one_apm/collector/stats_engine/stats_hash.rb +105 -0
  74. data/lib/one_apm/configuration.rb +429 -0
  75. data/lib/one_apm/configuration/autostart.rb +41 -0
  76. data/lib/one_apm/configuration/default_source.rb +1026 -0
  77. data/lib/one_apm/configuration/environment_source.rb +113 -0
  78. data/lib/one_apm/configuration/high_security_source.rb +56 -0
  79. data/lib/one_apm/configuration/manual_source.rb +13 -0
  80. data/lib/one_apm/configuration/server_source.rb +60 -0
  81. data/lib/one_apm/configuration/yaml_source.rb +134 -0
  82. data/lib/one_apm/errors/agent_errors.rb +26 -0
  83. data/lib/one_apm/errors/internal_agent_error.rb +16 -0
  84. data/lib/one_apm/errors/noticed_error.rb +79 -0
  85. data/lib/one_apm/frameworks/external.rb +15 -0
  86. data/lib/one_apm/frameworks/rails.rb +103 -0
  87. data/lib/one_apm/frameworks/rails3.rb +37 -0
  88. data/lib/one_apm/frameworks/rails4.rb +21 -0
  89. data/lib/one_apm/frameworks/ruby.rb +21 -0
  90. data/lib/one_apm/frameworks/sinatra.rb +12 -0
  91. data/lib/one_apm/inst/3rd/active_merchant.rb +35 -0
  92. data/lib/one_apm/inst/3rd/acts_as_solr.rb +70 -0
  93. data/lib/one_apm/inst/3rd/authlogic.rb +23 -0
  94. data/lib/one_apm/inst/3rd/sunspot.rb +31 -0
  95. data/lib/one_apm/inst/background_job/active_job.rb +88 -0
  96. data/lib/one_apm/inst/background_job/delayed_job.rb +52 -0
  97. data/lib/one_apm/inst/background_job/delayed_job_injection.rb +8 -0
  98. data/lib/one_apm/inst/background_job/resque.rb +107 -0
  99. data/lib/one_apm/inst/background_job/sidekiq.rb +64 -0
  100. data/lib/one_apm/inst/dispatcher/passenger.rb +25 -0
  101. data/lib/one_apm/inst/dispatcher/rainbows.rb +23 -0
  102. data/lib/one_apm/inst/framework/grape.rb +94 -0
  103. data/lib/one_apm/inst/framework/padrino.rb +30 -0
  104. data/lib/one_apm/inst/framework/sinatra.rb +185 -0
  105. data/lib/one_apm/inst/framework/sinatra/ignorer.rb +50 -0
  106. data/lib/one_apm/inst/framework/sinatra/transaction_namer.rb +54 -0
  107. data/lib/one_apm/inst/http_clients/curb.rb +189 -0
  108. data/lib/one_apm/inst/http_clients/excon.rb +70 -0
  109. data/lib/one_apm/inst/http_clients/excon/connection.rb +31 -0
  110. data/lib/one_apm/inst/http_clients/excon/middleware.rb +55 -0
  111. data/lib/one_apm/inst/http_clients/httpclient.rb +44 -0
  112. data/lib/one_apm/inst/http_clients/net.rb +34 -0
  113. data/lib/one_apm/inst/http_clients/typhoeus.rb +76 -0
  114. data/lib/one_apm/inst/nosql/memcache.rb +134 -0
  115. data/lib/one_apm/inst/nosql/mongo.rb +126 -0
  116. data/lib/one_apm/inst/nosql/mongo_moped.rb +85 -0
  117. data/lib/one_apm/inst/nosql/redis.rb +83 -0
  118. data/lib/one_apm/inst/orm/active_record.rb +99 -0
  119. data/lib/one_apm/inst/orm/active_record_4.rb +28 -0
  120. data/lib/one_apm/inst/orm/data_mapper.rb +180 -0
  121. data/lib/one_apm/inst/orm/sequel.rb +47 -0
  122. data/lib/one_apm/inst/rack.rb +38 -0
  123. data/lib/one_apm/inst/rack/rack.rb +44 -0
  124. data/lib/one_apm/inst/rack/rack_builder.rb +51 -0
  125. data/lib/one_apm/inst/rails/action_controller.rb +118 -0
  126. data/lib/one_apm/inst/rails/action_web_service.rb +44 -0
  127. data/lib/one_apm/inst/rails/errors.rb +43 -0
  128. data/lib/one_apm/inst/rails3/action_controller.rb +172 -0
  129. data/lib/one_apm/inst/rails3/errors.rb +43 -0
  130. data/lib/one_apm/inst/rails4/action_controller.rb +27 -0
  131. data/lib/one_apm/inst/rails4/action_controller_subscriber.rb +121 -0
  132. data/lib/one_apm/inst/rails4/action_view.rb +23 -0
  133. data/lib/one_apm/inst/rails4/action_view_subscriber.rb +93 -0
  134. data/lib/one_apm/inst/rails4/active_record_subscriber.rb +96 -0
  135. data/lib/one_apm/inst/rails4/errors.rb +42 -0
  136. data/lib/one_apm/inst/rails_middleware.rb +40 -0
  137. data/lib/one_apm/inst/support/evented_subscriber.rb +98 -0
  138. data/lib/one_apm/inst/support/ignore_actions.rb +39 -0
  139. data/lib/one_apm/inst/support/queue_time.rb +76 -0
  140. data/lib/one_apm/inst/transaction_base.rb +405 -0
  141. data/lib/one_apm/logger/agent_logger.rb +206 -0
  142. data/lib/one_apm/logger/audit_logger.rb +78 -0
  143. data/lib/one_apm/logger/memory_logger.rb +50 -0
  144. data/lib/one_apm/logger/null_logger.rb +19 -0
  145. data/lib/one_apm/metrics/metric_data.rb +72 -0
  146. data/lib/one_apm/metrics/metric_spec.rb +82 -0
  147. data/lib/one_apm/metrics/stats.rb +173 -0
  148. data/lib/one_apm/probe.rb +16 -0
  149. data/lib/one_apm/probe/framework_loader.rb +53 -0
  150. data/lib/one_apm/probe/instance_methods.rb +105 -0
  151. data/lib/one_apm/probe/instrumentation.rb +60 -0
  152. data/lib/one_apm/rack/browser_monitoring.rb +144 -0
  153. data/lib/one_apm/rack/middleware_base.rb +27 -0
  154. data/lib/one_apm/rack/middleware_hooks.rb +17 -0
  155. data/lib/one_apm/rack/middleware_tracing.rb +81 -0
  156. data/lib/one_apm/rack/middleware_wrapper.rb +86 -0
  157. data/lib/one_apm/support/chained_call.rb +15 -0
  158. data/lib/one_apm/support/coerce.rb +81 -0
  159. data/lib/one_apm/support/collection_helper.rb +79 -0
  160. data/lib/one_apm/support/dotted_hash.rb +45 -0
  161. data/lib/one_apm/support/encoders.rb +34 -0
  162. data/lib/one_apm/support/environment_report.rb +127 -0
  163. data/lib/one_apm/support/event_buffer.rb +82 -0
  164. data/lib/one_apm/support/event_buffer/sampled_buffer.rb +45 -0
  165. data/lib/one_apm/support/event_buffer/sized_buffer.rb +21 -0
  166. data/lib/one_apm/support/event_buffer/synthetics_event_buffer.rb +40 -0
  167. data/lib/one_apm/support/helper.rb +49 -0
  168. data/lib/one_apm/support/hostname.rb +13 -0
  169. data/lib/one_apm/support/http_clients/curb_wrappers.rb +65 -0
  170. data/lib/one_apm/support/http_clients/excon_wrappers.rb +63 -0
  171. data/lib/one_apm/support/http_clients/httpclient_wrappers.rb +61 -0
  172. data/lib/one_apm/support/http_clients/net_http_wrappers.rb +48 -0
  173. data/lib/one_apm/support/http_clients/typhoeus_wrappers.rb +73 -0
  174. data/lib/one_apm/support/http_clients/uri_util.rb +39 -0
  175. data/lib/one_apm/support/json_marshaller.rb +68 -0
  176. data/lib/one_apm/support/json_wrapper.rb +130 -0
  177. data/lib/one_apm/support/language_support.rb +142 -0
  178. data/lib/one_apm/support/library_detection.rb +119 -0
  179. data/lib/one_apm/support/local_environment.rb +196 -0
  180. data/lib/one_apm/support/marshaller.rb +62 -0
  181. data/lib/one_apm/support/method_tracer.rb +334 -0
  182. data/lib/one_apm/support/method_tracer/helpers.rb +92 -0
  183. data/lib/one_apm/support/method_tracer/traced_method_stack.rb +103 -0
  184. data/lib/one_apm/support/obfuscator.rb +47 -0
  185. data/lib/one_apm/support/okjson.rb +601 -0
  186. data/lib/one_apm/support/parameter_filtering.rb +35 -0
  187. data/lib/one_apm/support/rules_engine.rb +56 -0
  188. data/lib/one_apm/support/rules_engine/replacement_rule.rb +80 -0
  189. data/lib/one_apm/support/rules_engine/segment_terms_rule.rb +46 -0
  190. data/lib/one_apm/support/server.rb +11 -0
  191. data/lib/one_apm/support/supported_versions.rb +257 -0
  192. data/lib/one_apm/support/system_info.rb +211 -0
  193. data/lib/one_apm/support/timer_lib.rb +29 -0
  194. data/lib/one_apm/support/version_number.rb +51 -0
  195. data/lib/one_apm/support/vm.rb +30 -0
  196. data/lib/one_apm/support/vm/jruby_vm.rb +38 -0
  197. data/lib/one_apm/support/vm/monotonic_gc_profiler.rb +43 -0
  198. data/lib/one_apm/support/vm/mri_vm.rb +85 -0
  199. data/lib/one_apm/support/vm/rubinius_vm.rb +129 -0
  200. data/lib/one_apm/support/vm/snapshot.rb +18 -0
  201. data/lib/one_apm/transaction.rb +336 -0
  202. data/lib/one_apm/transaction/class_methods.rb +132 -0
  203. data/lib/one_apm/transaction/instance_helpers.rb +82 -0
  204. data/lib/one_apm/transaction/metric_constants.rb +42 -0
  205. data/lib/one_apm/transaction/sample_buffer/force_persist_sample_buffer.rb +21 -0
  206. data/lib/one_apm/transaction/sample_buffer/slowest_sample_buffer.rb +21 -0
  207. data/lib/one_apm/transaction/sample_buffer/synthetics_sample_buffer.rb +21 -0
  208. data/lib/one_apm/transaction/sample_buffer/transaction_sample_buffer.rb +101 -0
  209. data/lib/one_apm/transaction/sample_buffer/xray_sample_buffer.rb +60 -0
  210. data/lib/one_apm/transaction/segment.rb +193 -0
  211. data/lib/one_apm/transaction/segment_summary.rb +51 -0
  212. data/lib/one_apm/transaction/thread_local_access.rb +73 -0
  213. data/lib/one_apm/transaction/transaction_analysis.rb +78 -0
  214. data/lib/one_apm/transaction/transaction_apdex.rb +20 -0
  215. data/lib/one_apm/transaction/transaction_cpu.rb +22 -0
  216. data/lib/one_apm/transaction/transaction_finish_append.rb +67 -0
  217. data/lib/one_apm/transaction/transaction_ignore.rb +33 -0
  218. data/lib/one_apm/transaction/transaction_jruby_functions.rb +40 -0
  219. data/lib/one_apm/transaction/transaction_metrics.rb +53 -0
  220. data/lib/one_apm/transaction/transaction_name.rb +90 -0
  221. data/lib/one_apm/transaction/transaction_namer.rb +49 -0
  222. data/lib/one_apm/transaction/transaction_sample.rb +204 -0
  223. data/lib/one_apm/transaction/transaction_sample_builder.rb +168 -0
  224. data/lib/one_apm/transaction/transaction_state.rb +149 -0
  225. data/lib/one_apm/transaction/transaction_summary.rb +28 -0
  226. data/lib/one_apm/transaction/transaction_synthetics.rb +40 -0
  227. data/lib/one_apm/transaction/transaction_timings.rb +54 -0
  228. data/lib/one_apm/version.rb +13 -0
  229. data/lib/oneapm_rpm.rb +16 -0
  230. data/lib/sequel/extensions/oneapm_instrumentation.rb +84 -0
  231. data/lib/sequel/plugins/oneapm_instrumentation.rb +66 -0
  232. data/oneapm.yml +135 -0
  233. data/oneapm_rpm.gemspec +58 -0
  234. metadata +474 -0
@@ -0,0 +1,308 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singleton'
4
+ require 'one_apm/agent/database/obfuscation_helpers'
5
+ require 'one_apm/agent/database/obfuscator'
6
+ require 'one_apm/agent/database/postgres_explain_obfuscator'
7
+
8
+ module OneApm
9
+ # columns for a mysql explain plan
10
+ MYSQL_EXPLAIN_COLUMNS = [
11
+ "Id",
12
+ "Select Type",
13
+ "Table",
14
+ "Type",
15
+ "Possible Keys",
16
+ "Key",
17
+ "Key Length",
18
+ "Ref",
19
+ "Rows",
20
+ "Extra"
21
+ ].freeze
22
+
23
+ module Agent
24
+ module Database
25
+ MAX_QUERY_LENGTH = 16384
26
+
27
+ extend self
28
+
29
+ def capture_query(query)
30
+ Helper.correctly_encoded(truncate_query(query))
31
+ end
32
+
33
+ def truncate_query(query)
34
+ if query.length > (MAX_QUERY_LENGTH - 4)
35
+ query[0..MAX_QUERY_LENGTH - 4] + '...'
36
+ else
37
+ query
38
+ end
39
+ end
40
+
41
+ def obfuscate_sql(sql)
42
+ Obfuscator.instance.obfuscator.call(sql)
43
+ end
44
+
45
+ def set_sql_obfuscator(type, &block)
46
+ Obfuscator.instance.set_sql_obfuscator(type, &block)
47
+ end
48
+
49
+ def record_sql_method(config_section=:transaction_tracer)
50
+ case Agent.config["#{config_section}.record_sql".to_sym].to_s
51
+ when 'off'
52
+ :off
53
+ when 'none'
54
+ :off
55
+ when 'false'
56
+ :off
57
+ when 'raw'
58
+ :raw
59
+ else
60
+ :obfuscated
61
+ end
62
+ end
63
+
64
+ RECORD_FOR = [:raw, :obfuscated].freeze
65
+
66
+ def should_record_sql?(config_section=:transaction_tracer)
67
+ RECORD_FOR.include?(record_sql_method(config_section))
68
+ end
69
+
70
+ def should_collect_explain_plans?(config_section=:transaction_tracer)
71
+ should_record_sql?(config_section) &&
72
+ Agent.config["#{config_section}.explain_enabled".to_sym]
73
+ end
74
+
75
+ def get_connection(config, &connector)
76
+ ConnectionManager.instance.get_connection(config, &connector)
77
+ end
78
+
79
+ def close_connections
80
+ ConnectionManager.instance.close_connections
81
+ end
82
+
83
+ # This takes a connection config hash from ActiveRecord or Sequel and
84
+ # returns a string describing the associated database adapter
85
+ def adapter_from_config(config)
86
+ if config[:adapter]
87
+ return config[:adapter].to_s
88
+ elsif config[:uri] && config[:uri].to_s =~ /^jdbc:([^:]+):/
89
+ # This case is for Sequel with the jdbc-mysql, jdbc-postgres, or
90
+ # jdbc-sqlite3 gems.
91
+ return $1
92
+ end
93
+ end
94
+
95
+ # Perform this in the runtime environment of a managed
96
+ # application, to explain the sql statement executed within a
97
+ # segment of a transaction sample. Returns an array of
98
+ # explanations (which is an array rows consisting of an array of
99
+ # strings for each column returned by the the explain query)
100
+ # Note this happens only for statements whose execution time
101
+ # exceeds a threshold (e.g. 500ms) and only within the slowest
102
+ # transaction in a report period, selected for shipment to OneApm
103
+ def explain_sql(sql, connection_config, &explainer)
104
+ return nil unless sql && connection_config
105
+ statement = sql.split(";\n")[0] # only explain the first
106
+ explain_plan = explain_statement(statement, connection_config, &explainer)
107
+ return explain_plan || []
108
+ end
109
+
110
+ SUPPORTED_ADAPTERS_FOR_EXPLAIN = %w[postgres postgresql mysql2 mysql sqlite].freeze
111
+
112
+ def explain_statement(statement, config, &explainer)
113
+ return unless is_select?(statement)
114
+
115
+ if statement[-3,3] == '...'
116
+ OneApm::Agent.logger.debug('Unable to collect explain plan for truncated query.')
117
+ return
118
+ end
119
+
120
+ if parameterized?(statement)
121
+ OneApm::Agent.logger.debug('Unable to collect explain plan for parameterized query.')
122
+ return
123
+ end
124
+
125
+ adapter = adapter_from_config(config)
126
+ if !SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
127
+ OneApm::Agent.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.")
128
+ return
129
+ end
130
+
131
+ handle_exception_in_explain do
132
+ start = Time.now
133
+ plan = explainer.call(config, statement)
134
+ ::OneApm::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
135
+ return process_resultset(plan, adapter) if plan
136
+ end
137
+ end
138
+
139
+ def process_resultset(results, adapter)
140
+ case adapter.to_s
141
+ when 'postgres', 'postgresql'
142
+ process_explain_results_postgres(results)
143
+ when 'mysql2'
144
+ process_explain_results_mysql2(results)
145
+ when 'mysql'
146
+ process_explain_results_mysql(results)
147
+ when 'sqlite'
148
+ process_explain_results_sqlite(results)
149
+ end
150
+ end
151
+
152
+ QUERY_PLAN = 'QUERY PLAN'.freeze
153
+
154
+ def process_explain_results_postgres(results)
155
+ if results.is_a?(String)
156
+ query_plan_string = results
157
+ else
158
+ lines = []
159
+ results.each { |row| lines << row[QUERY_PLAN] }
160
+ query_plan_string = lines.join("\n")
161
+ end
162
+
163
+ unless record_sql_method == :raw
164
+ query_plan_string = OneApm::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
165
+ end
166
+ values = query_plan_string.split("\n").map { |line| [line] }
167
+
168
+ [[QUERY_PLAN], values]
169
+ end
170
+
171
+ # Sequel returns explain plans as just one big pre-formatted String
172
+ # In that case, we send a nil headers array, and the single string
173
+ # wrapped in an array for the values.
174
+ # Note that we don't use this method for Postgres explain plans, since
175
+ # they need to be passed through the explain plan obfuscator first.
176
+ def string_explain_plan_results(results)
177
+ [nil, [results]]
178
+ end
179
+
180
+ def process_explain_results_mysql(results)
181
+ return string_explain_plan_results(results) if results.is_a?(String)
182
+ headers = []
183
+ values = []
184
+ if results.is_a?(Array)
185
+ # We're probably using the jdbc-mysql gem for JRuby, which will give
186
+ # us an array of hashes.
187
+ headers = results.first.keys
188
+ results.each do |row|
189
+ values << headers.map { |h| row[h] }
190
+ end
191
+ else
192
+ # We're probably using the native mysql driver gem, which will give us
193
+ # a Mysql::Result object that responds to each_hash
194
+ results.each_hash do |row|
195
+ headers = row.keys
196
+ values << headers.map { |h| row[h] }
197
+ end
198
+ end
199
+ [headers, values]
200
+ end
201
+
202
+ def process_explain_results_mysql2(results)
203
+ return string_explain_plan_results(results) if results.is_a?(String)
204
+ headers = results.fields
205
+ values = []
206
+ results.each { |row| values << row }
207
+ [headers, values]
208
+ end
209
+
210
+ SQLITE_EXPLAIN_COLUMNS = %w[addr opcode p1 p2 p3 p4 p5 comment]
211
+
212
+ def process_explain_results_sqlite(results)
213
+ return string_explain_plan_results(results) if results.is_a?(String)
214
+ headers = SQLITE_EXPLAIN_COLUMNS
215
+ values = []
216
+ results.each do |row|
217
+ values << headers.map { |h| row[h] }
218
+ end
219
+ [headers, values]
220
+ end
221
+
222
+ def handle_exception_in_explain
223
+ yield
224
+ rescue => e
225
+ begin
226
+ # guarantees no throw from explain_sql
227
+ ::OneApm::Agent.logger.error("Error getting query plan:", e)
228
+ nil
229
+ rescue
230
+ # double exception. throw up your hands
231
+ nil
232
+ end
233
+ end
234
+
235
+ KNOWN_OPERATIONS = [
236
+ 'alter',
237
+ 'select',
238
+ 'update',
239
+ 'delete',
240
+ 'insert',
241
+ 'create',
242
+ 'show',
243
+ 'set',
244
+ 'exec',
245
+ 'execute',
246
+ 'call'
247
+ ]
248
+
249
+ SQL_COMMENT_REGEX = Regexp.new('/\*.*?\*/', Regexp::MULTILINE).freeze
250
+
251
+ def parse_operation_from_query(sql)
252
+ sql = sql.gsub(SQL_COMMENT_REGEX, '')
253
+ if sql =~ /(\w+)/
254
+ op = $1.downcase
255
+ return op if KNOWN_OPERATIONS.include?(op)
256
+ end
257
+ end
258
+
259
+ def is_select?(statement)
260
+ parse_operation_from_query(statement) == 'select'
261
+ end
262
+
263
+ def parameterized?(statement)
264
+ Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/
265
+ end
266
+
267
+ class ConnectionManager
268
+ include Singleton
269
+
270
+ # Returns a cached connection for a given ActiveRecord
271
+ # configuration - these are stored or reopened as needed, and if
272
+ # we cannot get one, we ignore it and move on without explaining
273
+ # the sql
274
+ def get_connection(config, &connector)
275
+ @connections ||= {}
276
+
277
+ connection = @connections[config]
278
+
279
+ return connection if connection
280
+
281
+ begin
282
+ @connections[config] = connector.call(config)
283
+ rescue => e
284
+ ::OneApm::Agent.logger.error("Caught exception trying to get connection to DB for explain.", e)
285
+ nil
286
+ end
287
+ end
288
+
289
+ # Closes all the connections in the internal connection cache
290
+ def close_connections
291
+ @connections ||= {}
292
+ @connections.values.each do |connection|
293
+ begin
294
+ connection.disconnect!
295
+ rescue
296
+ end
297
+ end
298
+
299
+ @connections = {}
300
+ end
301
+ end
302
+
303
+ class Statement < String
304
+ attr_accessor :adapter, :config, :explainer
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ module OneApm
3
+ module Agent
4
+ module Instrumentation
5
+ module ActiveRecordHelper
6
+ module_function
7
+
8
+ def metric_for_name(name)
9
+ return unless name && name.respond_to?(:split)
10
+ parts = name.split(' ')
11
+ if parts.size == 2
12
+ model = parts.first
13
+ operation = parts.last.downcase
14
+ case operation
15
+ when 'load', 'count', 'exists'
16
+ op_name = 'find'
17
+ when 'indexes', 'columns'
18
+ op_name = nil # fall back to DirectSQL
19
+ when 'destroy', 'find', 'save', 'create'
20
+ op_name = operation
21
+ when 'update'
22
+ op_name = 'save'
23
+ else
24
+ if model == 'Join'
25
+ op_name = operation
26
+ end
27
+ end
28
+ "ActiveRecord/#{model}/#{op_name}" if op_name
29
+ end
30
+ end
31
+
32
+ def metric_for_sql(sql) #THREAD_LOCAL_ACCESS
33
+ txn = OneApm::Transaction.tl_current
34
+ metric = txn && txn.database_metric_name
35
+ if metric.nil?
36
+ operation = OneApm::Agent::Database.parse_operation_from_query(sql)
37
+ if operation
38
+ # Could not determine the model/operation so use a fallback metric
39
+ metric = "Database/SQL/#{operation}"
40
+ else
41
+ metric = "Database/SQL/other"
42
+ end
43
+ end
44
+ metric
45
+ end
46
+
47
+ # Given a metric name such as "ActiveRecord/model/action" this
48
+ # returns an array of rollup metrics:
49
+ # [ "Datastore/all", "ActiveRecord/all", "ActiveRecord/action" ]
50
+ # If the metric name is in the form of "ActiveRecord/action"
51
+ # this returns merely: [ "Datastore/all", "ActiveRecord/all" ]
52
+ def rollup_metrics_for(metric)
53
+ metrics = ["Datastore/all"]
54
+
55
+ # If we're outside of a web transaction, don't record any rollup
56
+ # database metrics. This is to prevent metrics from background tasks
57
+ # from polluting the metrics used to drive overview graphs.
58
+ if OneApm::Transaction.recording_web_transaction?
59
+ metrics << "ActiveRecord/all"
60
+ else
61
+ metrics << "Datastore/allOther"
62
+ end
63
+ metrics << "ActiveRecord/#{$1}" if metric =~ /ActiveRecord\/[\w|\:]+\/(\w+)/
64
+
65
+ metrics
66
+ end
67
+
68
+ # Given a database adapter name and a database server host
69
+ # this returns a metric name in the form:
70
+ # "RemoteService/sql/adapter/host"
71
+ # Host defaults to "localhost".
72
+ def remote_service_metric(adapter, host)
73
+ host ||= 'localhost'
74
+ type = adapter.to_s.sub(/\d*/, '')
75
+ "RemoteService/sql/#{type}/#{host}"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Agent
5
+ module Database
6
+ module ObfuscationHelpers
7
+ # Note that the following two regexes are applied to a reversed version
8
+ # of the query. This is why the backslash escape sequences (\' and \")
9
+ # appear reversed within them.
10
+ #
11
+ # Note that some database adapters (notably, PostgreSQL with
12
+ # standard_conforming_strings on and MySQL with NO_BACKSLASH_ESCAPES on)
13
+ # do not apply special treatment to backslashes within quoted string
14
+ # literals. We don't have an easy way of determining whether the
15
+ # database connection from which a query was captured was operating in
16
+ # one of these modes, but the obfuscation is done in such a way that it
17
+ # should not matter.
18
+ #
19
+ # Reversing the query string before obfuscation allows us to get around
20
+ # the fact that a \' appearing within a string may or may not terminate
21
+ # the string, because we know that a string cannot *start* with a \'.
22
+ REVERSE_SINGLE_QUOTES_REGEX = /'(?:''|'\\|[^'])*'/
23
+ REVERSE_ANY_QUOTES_REGEX = /'(?:''|'\\|[^'])*'|"(?:""|"\\|[^"])*"/
24
+
25
+ NUMERICS_REGEX = /\b\d+\b/
26
+
27
+ # We take a conservative, overly-aggressive approach to obfuscating
28
+ # comments, and drop everything from the query after encountering any
29
+ # character sequence that could be a comment initiator. We do this after
30
+ # removal of string literals to avoid accidentally over-obfuscating when
31
+ # a string literal contains a comment initiator.
32
+ SQL_COMMENT_REGEX = Regexp.new('(?:/\*|--|#).*', Regexp::MULTILINE).freeze
33
+
34
+ # We use these to check whether the query contains any quote characters
35
+ # after obfuscation. If so, that's a good indication that the original
36
+ # query was malformed, and so our obfuscation can't reliabily find
37
+ # literals. In such a case, we'll replace the entire query with a
38
+ # placeholder.
39
+ LITERAL_SINGLE_QUOTE = "'".freeze
40
+ LITERAL_DOUBLE_QUOTE = '"'.freeze
41
+
42
+ PLACEHOLDER = '?'.freeze
43
+
44
+ def obfuscate_single_quote_literals(sql)
45
+ obfuscated = sql.reverse
46
+ obfuscated.gsub!(REVERSE_SINGLE_QUOTES_REGEX, PLACEHOLDER)
47
+ obfuscated.reverse!
48
+ obfuscated
49
+ end
50
+
51
+ def obfuscate_quoted_literals(sql)
52
+ obfuscated = sql.reverse
53
+ obfuscated.gsub!(REVERSE_ANY_QUOTES_REGEX, PLACEHOLDER)
54
+ obfuscated.reverse!
55
+ obfuscated
56
+ end
57
+
58
+ def obfuscate_numeric_literals(sql)
59
+ sql.gsub(NUMERICS_REGEX, PLACEHOLDER)
60
+ end
61
+
62
+ def remove_comments(sql)
63
+ sql.gsub(SQL_COMMENT_REGEX, PLACEHOLDER)
64
+ end
65
+
66
+ def contains_single_quotes?(str)
67
+ str.include?(LITERAL_SINGLE_QUOTE)
68
+ end
69
+
70
+ def contains_quotes?(str)
71
+ str.include?(LITERAL_SINGLE_QUOTE) || str.include?(LITERAL_DOUBLE_QUOTE)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end