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.
- checksums.yaml +7 -0
- data/.gitignore +30 -0
- data/.rubocop.yml +725 -0
- data/Gemfile +3 -0
- data/Guardfile +7 -0
- data/LICENSE +1 -0
- data/README.md +3 -0
- data/config/cert/cacert.pem +1177 -0
- data/config/database.yml +5 -0
- data/lib/initializers/goliath.rb +11 -0
- data/lib/initializers/other.rb +1 -0
- data/lib/initializers/rails.rb +15 -0
- data/lib/one_apm/agent.rb +253 -0
- data/lib/one_apm/agent/agent.rb +283 -0
- data/lib/one_apm/agent/agent/connect.rb +175 -0
- data/lib/one_apm/agent/agent/container_data_manager.rb +218 -0
- data/lib/one_apm/agent/agent/forkable_dispatcher_functions.rb +96 -0
- data/lib/one_apm/agent/agent/helpers.rb +45 -0
- data/lib/one_apm/agent/agent/start.rb +226 -0
- data/lib/one_apm/agent/agent/start_worker_thread.rb +148 -0
- data/lib/one_apm/agent/busy_calculator.rb +115 -0
- data/lib/one_apm/agent/cross_app/cross_app_monitor.rb +181 -0
- data/lib/one_apm/agent/cross_app/cross_app_tracing.rb +336 -0
- data/lib/one_apm/agent/database.rb +308 -0
- data/lib/one_apm/agent/database/active_record_helper.rb +80 -0
- data/lib/one_apm/agent/database/obfuscation_helpers.rb +76 -0
- data/lib/one_apm/agent/database/obfuscator.rb +78 -0
- data/lib/one_apm/agent/database/postgres_explain_obfuscator.rb +45 -0
- data/lib/one_apm/agent/datastores.rb +175 -0
- data/lib/one_apm/agent/datastores/metric_helper.rb +83 -0
- data/lib/one_apm/agent/datastores/mongo.rb +27 -0
- data/lib/one_apm/agent/datastores/mongo/metric_translator.rb +189 -0
- data/lib/one_apm/agent/datastores/mongo/obfuscator.rb +37 -0
- data/lib/one_apm/agent/datastores/mongo/statement_formatter.rb +51 -0
- data/lib/one_apm/agent/event/event_listener.rb +40 -0
- data/lib/one_apm/agent/event/event_loop.rb +191 -0
- data/lib/one_apm/agent/event/worker_loop.rb +97 -0
- data/lib/one_apm/agent/harvester.rb +48 -0
- data/lib/one_apm/agent/inbound_request_monitor.rb +30 -0
- data/lib/one_apm/agent/javascript_instrumentor.rb +186 -0
- data/lib/one_apm/agent/pipe/pipe_channel_manager.rb +275 -0
- data/lib/one_apm/agent/pipe/pipe_service.rb +81 -0
- data/lib/one_apm/agent/sampler.rb +55 -0
- data/lib/one_apm/agent/sampler_collection.rb +65 -0
- data/lib/one_apm/agent/samplers/cpu_sampler.rb +49 -0
- data/lib/one_apm/agent/samplers/delayed_job_sampler.rb +109 -0
- data/lib/one_apm/agent/samplers/memory_sampler.rb +144 -0
- data/lib/one_apm/agent/samplers/object_sampler.rb +22 -0
- data/lib/one_apm/agent/samplers/vm_sampler.rb +124 -0
- data/lib/one_apm/agent/synthetics_monitor.rb +48 -0
- data/lib/one_apm/agent/threading/agent_thread.rb +74 -0
- data/lib/one_apm/agent/threading/backtrace_node.rb +133 -0
- data/lib/one_apm/agent/threading/backtrace_service.rb +259 -0
- data/lib/one_apm/agent/threading/thread_profile.rb +155 -0
- data/lib/one_apm/collector/collector/helper.rb +139 -0
- data/lib/one_apm/collector/collector/http_connection.rb +254 -0
- data/lib/one_apm/collector/collector/server_methods.rb +71 -0
- data/lib/one_apm/collector/collector_service.rb +123 -0
- data/lib/one_apm/collector/commands/agent_command.rb +17 -0
- data/lib/one_apm/collector/commands/thread_profiler_session.rb +108 -0
- data/lib/one_apm/collector/commands/xray_session.rb +53 -0
- data/lib/one_apm/collector/commands/xray_session_collection.rb +156 -0
- data/lib/one_apm/collector/containers/agent_command_router.rb +153 -0
- data/lib/one_apm/collector/containers/custom_event_aggregator.rb +94 -0
- data/lib/one_apm/collector/containers/error_collector.rb +349 -0
- data/lib/one_apm/collector/containers/sql_sampler.rb +331 -0
- data/lib/one_apm/collector/containers/stats_engine.rb +34 -0
- data/lib/one_apm/collector/containers/transaction_event_aggregator.rb +249 -0
- data/lib/one_apm/collector/containers/transaction_sampler.rb +352 -0
- data/lib/one_apm/collector/containers/utilization_data.rb +36 -0
- data/lib/one_apm/collector/stats_engine/gc_profiler.rb +106 -0
- data/lib/one_apm/collector/stats_engine/metric_stats.rb +243 -0
- data/lib/one_apm/collector/stats_engine/stats_hash.rb +105 -0
- data/lib/one_apm/configuration.rb +429 -0
- data/lib/one_apm/configuration/autostart.rb +41 -0
- data/lib/one_apm/configuration/default_source.rb +1026 -0
- data/lib/one_apm/configuration/environment_source.rb +113 -0
- data/lib/one_apm/configuration/high_security_source.rb +56 -0
- data/lib/one_apm/configuration/manual_source.rb +13 -0
- data/lib/one_apm/configuration/server_source.rb +60 -0
- data/lib/one_apm/configuration/yaml_source.rb +134 -0
- data/lib/one_apm/errors/agent_errors.rb +26 -0
- data/lib/one_apm/errors/internal_agent_error.rb +16 -0
- data/lib/one_apm/errors/noticed_error.rb +79 -0
- data/lib/one_apm/frameworks/external.rb +15 -0
- data/lib/one_apm/frameworks/rails.rb +103 -0
- data/lib/one_apm/frameworks/rails3.rb +37 -0
- data/lib/one_apm/frameworks/rails4.rb +21 -0
- data/lib/one_apm/frameworks/ruby.rb +21 -0
- data/lib/one_apm/frameworks/sinatra.rb +12 -0
- data/lib/one_apm/inst/3rd/active_merchant.rb +35 -0
- data/lib/one_apm/inst/3rd/acts_as_solr.rb +70 -0
- data/lib/one_apm/inst/3rd/authlogic.rb +23 -0
- data/lib/one_apm/inst/3rd/sunspot.rb +31 -0
- data/lib/one_apm/inst/background_job/active_job.rb +88 -0
- data/lib/one_apm/inst/background_job/delayed_job.rb +52 -0
- data/lib/one_apm/inst/background_job/delayed_job_injection.rb +8 -0
- data/lib/one_apm/inst/background_job/resque.rb +107 -0
- data/lib/one_apm/inst/background_job/sidekiq.rb +64 -0
- data/lib/one_apm/inst/dispatcher/passenger.rb +25 -0
- data/lib/one_apm/inst/dispatcher/rainbows.rb +23 -0
- data/lib/one_apm/inst/framework/grape.rb +94 -0
- data/lib/one_apm/inst/framework/padrino.rb +30 -0
- data/lib/one_apm/inst/framework/sinatra.rb +185 -0
- data/lib/one_apm/inst/framework/sinatra/ignorer.rb +50 -0
- data/lib/one_apm/inst/framework/sinatra/transaction_namer.rb +54 -0
- data/lib/one_apm/inst/http_clients/curb.rb +189 -0
- data/lib/one_apm/inst/http_clients/excon.rb +70 -0
- data/lib/one_apm/inst/http_clients/excon/connection.rb +31 -0
- data/lib/one_apm/inst/http_clients/excon/middleware.rb +55 -0
- data/lib/one_apm/inst/http_clients/httpclient.rb +44 -0
- data/lib/one_apm/inst/http_clients/net.rb +34 -0
- data/lib/one_apm/inst/http_clients/typhoeus.rb +76 -0
- data/lib/one_apm/inst/nosql/memcache.rb +134 -0
- data/lib/one_apm/inst/nosql/mongo.rb +126 -0
- data/lib/one_apm/inst/nosql/mongo_moped.rb +85 -0
- data/lib/one_apm/inst/nosql/redis.rb +83 -0
- data/lib/one_apm/inst/orm/active_record.rb +99 -0
- data/lib/one_apm/inst/orm/active_record_4.rb +28 -0
- data/lib/one_apm/inst/orm/data_mapper.rb +180 -0
- data/lib/one_apm/inst/orm/sequel.rb +47 -0
- data/lib/one_apm/inst/rack.rb +38 -0
- data/lib/one_apm/inst/rack/rack.rb +44 -0
- data/lib/one_apm/inst/rack/rack_builder.rb +51 -0
- data/lib/one_apm/inst/rails/action_controller.rb +118 -0
- data/lib/one_apm/inst/rails/action_web_service.rb +44 -0
- data/lib/one_apm/inst/rails/errors.rb +43 -0
- data/lib/one_apm/inst/rails3/action_controller.rb +172 -0
- data/lib/one_apm/inst/rails3/errors.rb +43 -0
- data/lib/one_apm/inst/rails4/action_controller.rb +27 -0
- data/lib/one_apm/inst/rails4/action_controller_subscriber.rb +121 -0
- data/lib/one_apm/inst/rails4/action_view.rb +23 -0
- data/lib/one_apm/inst/rails4/action_view_subscriber.rb +93 -0
- data/lib/one_apm/inst/rails4/active_record_subscriber.rb +96 -0
- data/lib/one_apm/inst/rails4/errors.rb +42 -0
- data/lib/one_apm/inst/rails_middleware.rb +40 -0
- data/lib/one_apm/inst/support/evented_subscriber.rb +98 -0
- data/lib/one_apm/inst/support/ignore_actions.rb +39 -0
- data/lib/one_apm/inst/support/queue_time.rb +76 -0
- data/lib/one_apm/inst/transaction_base.rb +405 -0
- data/lib/one_apm/logger/agent_logger.rb +206 -0
- data/lib/one_apm/logger/audit_logger.rb +78 -0
- data/lib/one_apm/logger/memory_logger.rb +50 -0
- data/lib/one_apm/logger/null_logger.rb +19 -0
- data/lib/one_apm/metrics/metric_data.rb +72 -0
- data/lib/one_apm/metrics/metric_spec.rb +82 -0
- data/lib/one_apm/metrics/stats.rb +173 -0
- data/lib/one_apm/probe.rb +16 -0
- data/lib/one_apm/probe/framework_loader.rb +53 -0
- data/lib/one_apm/probe/instance_methods.rb +105 -0
- data/lib/one_apm/probe/instrumentation.rb +60 -0
- data/lib/one_apm/rack/browser_monitoring.rb +144 -0
- data/lib/one_apm/rack/middleware_base.rb +27 -0
- data/lib/one_apm/rack/middleware_hooks.rb +17 -0
- data/lib/one_apm/rack/middleware_tracing.rb +81 -0
- data/lib/one_apm/rack/middleware_wrapper.rb +86 -0
- data/lib/one_apm/support/chained_call.rb +15 -0
- data/lib/one_apm/support/coerce.rb +81 -0
- data/lib/one_apm/support/collection_helper.rb +79 -0
- data/lib/one_apm/support/dotted_hash.rb +45 -0
- data/lib/one_apm/support/encoders.rb +34 -0
- data/lib/one_apm/support/environment_report.rb +127 -0
- data/lib/one_apm/support/event_buffer.rb +82 -0
- data/lib/one_apm/support/event_buffer/sampled_buffer.rb +45 -0
- data/lib/one_apm/support/event_buffer/sized_buffer.rb +21 -0
- data/lib/one_apm/support/event_buffer/synthetics_event_buffer.rb +40 -0
- data/lib/one_apm/support/helper.rb +49 -0
- data/lib/one_apm/support/hostname.rb +13 -0
- data/lib/one_apm/support/http_clients/curb_wrappers.rb +65 -0
- data/lib/one_apm/support/http_clients/excon_wrappers.rb +63 -0
- data/lib/one_apm/support/http_clients/httpclient_wrappers.rb +61 -0
- data/lib/one_apm/support/http_clients/net_http_wrappers.rb +48 -0
- data/lib/one_apm/support/http_clients/typhoeus_wrappers.rb +73 -0
- data/lib/one_apm/support/http_clients/uri_util.rb +39 -0
- data/lib/one_apm/support/json_marshaller.rb +68 -0
- data/lib/one_apm/support/json_wrapper.rb +130 -0
- data/lib/one_apm/support/language_support.rb +142 -0
- data/lib/one_apm/support/library_detection.rb +119 -0
- data/lib/one_apm/support/local_environment.rb +196 -0
- data/lib/one_apm/support/marshaller.rb +62 -0
- data/lib/one_apm/support/method_tracer.rb +334 -0
- data/lib/one_apm/support/method_tracer/helpers.rb +92 -0
- data/lib/one_apm/support/method_tracer/traced_method_stack.rb +103 -0
- data/lib/one_apm/support/obfuscator.rb +47 -0
- data/lib/one_apm/support/okjson.rb +601 -0
- data/lib/one_apm/support/parameter_filtering.rb +35 -0
- data/lib/one_apm/support/rules_engine.rb +56 -0
- data/lib/one_apm/support/rules_engine/replacement_rule.rb +80 -0
- data/lib/one_apm/support/rules_engine/segment_terms_rule.rb +46 -0
- data/lib/one_apm/support/server.rb +11 -0
- data/lib/one_apm/support/supported_versions.rb +257 -0
- data/lib/one_apm/support/system_info.rb +211 -0
- data/lib/one_apm/support/timer_lib.rb +29 -0
- data/lib/one_apm/support/version_number.rb +51 -0
- data/lib/one_apm/support/vm.rb +30 -0
- data/lib/one_apm/support/vm/jruby_vm.rb +38 -0
- data/lib/one_apm/support/vm/monotonic_gc_profiler.rb +43 -0
- data/lib/one_apm/support/vm/mri_vm.rb +85 -0
- data/lib/one_apm/support/vm/rubinius_vm.rb +129 -0
- data/lib/one_apm/support/vm/snapshot.rb +18 -0
- data/lib/one_apm/transaction.rb +336 -0
- data/lib/one_apm/transaction/class_methods.rb +132 -0
- data/lib/one_apm/transaction/instance_helpers.rb +82 -0
- data/lib/one_apm/transaction/metric_constants.rb +42 -0
- data/lib/one_apm/transaction/sample_buffer/force_persist_sample_buffer.rb +21 -0
- data/lib/one_apm/transaction/sample_buffer/slowest_sample_buffer.rb +21 -0
- data/lib/one_apm/transaction/sample_buffer/synthetics_sample_buffer.rb +21 -0
- data/lib/one_apm/transaction/sample_buffer/transaction_sample_buffer.rb +101 -0
- data/lib/one_apm/transaction/sample_buffer/xray_sample_buffer.rb +60 -0
- data/lib/one_apm/transaction/segment.rb +193 -0
- data/lib/one_apm/transaction/segment_summary.rb +51 -0
- data/lib/one_apm/transaction/thread_local_access.rb +73 -0
- data/lib/one_apm/transaction/transaction_analysis.rb +78 -0
- data/lib/one_apm/transaction/transaction_apdex.rb +20 -0
- data/lib/one_apm/transaction/transaction_cpu.rb +22 -0
- data/lib/one_apm/transaction/transaction_finish_append.rb +67 -0
- data/lib/one_apm/transaction/transaction_ignore.rb +33 -0
- data/lib/one_apm/transaction/transaction_jruby_functions.rb +40 -0
- data/lib/one_apm/transaction/transaction_metrics.rb +53 -0
- data/lib/one_apm/transaction/transaction_name.rb +90 -0
- data/lib/one_apm/transaction/transaction_namer.rb +49 -0
- data/lib/one_apm/transaction/transaction_sample.rb +204 -0
- data/lib/one_apm/transaction/transaction_sample_builder.rb +168 -0
- data/lib/one_apm/transaction/transaction_state.rb +149 -0
- data/lib/one_apm/transaction/transaction_summary.rb +28 -0
- data/lib/one_apm/transaction/transaction_synthetics.rb +40 -0
- data/lib/one_apm/transaction/transaction_timings.rb +54 -0
- data/lib/one_apm/version.rb +13 -0
- data/lib/oneapm_rpm.rb +16 -0
- data/lib/sequel/extensions/oneapm_instrumentation.rb +84 -0
- data/lib/sequel/plugins/oneapm_instrumentation.rb +66 -0
- data/oneapm.yml +135 -0
- data/oneapm_rpm.gemspec +58 -0
- 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
|