module Mmtrix::Agent::Database
Constants
- EMPTY_STRING
- KNOWN_OPERATIONS
- MAX_QUERY_LENGTH
- QUERY_PLAN
- RECORD_FOR
- SQLITE_EXPLAIN_COLUMNS
- SQL_COMMENT_REGEX
- SUPPORTED_ADAPTERS_FOR_EXPLAIN
Public Instance Methods
This takes a connection config hash from ActiveRecord or Sequel
and returns a string describing the associated database adapter
# File lib/mmtrix/agent/database.rb, line 100 def adapter_from_config(config) if config[:adapter] return config[:adapter].to_s elsif config[:uri] && config[:uri].to_s =~ /^jdbc:([^:]+):/ # This case is for Sequel with the jdbc-mysql, jdbc-postgres, or # jdbc-sqlite3 gems. return $1 end end
# File lib/mmtrix/agent/database.rb, line 31 def capture_query(query) Helper.correctly_encoded(truncate_query(query)) end
# File lib/mmtrix/agent/database.rb, line 94 def close_connections ConnectionManager.instance.close_connections end
Perform this in the runtime environment of a managed application, to explain the sql statement executed within a node of a transaction sample. Returns an array of explanations (which is an array rows consisting of an array of strings for each column returned by the the explain query) Note this happens only for statements whose execution time exceeds a threshold (e.g. 500ms) and only within the slowest transaction in a report period, selected for shipment to New Mmtrix
# File lib/mmtrix/agent/database.rb, line 119 def explain_sql(sql, connection_config, explainer=nil) return nil unless sql && explainer && connection_config statement = sql.split(";\n")[0] # only explain the first explain_plan = explain_statement(statement, connection_config, explainer) return explain_plan || [] end
# File lib/mmtrix/agent/database.rb, line 128 def explain_statement(statement, config, explainer) return unless explainer && is_select?(statement) if statement[-3,3] == '...' Mmtrix::Agent.logger.debug('Unable to collect explain plan for truncated query.') return end if parameterized?(statement) Mmtrix::Agent.logger.debug('Unable to collect explain plan for parameterized query.') return end adapter = adapter_from_config(config) if !SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter) Mmtrix::Agent.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.") return end handle_exception_in_explain do start = Time.now plan = explainer.call(config, statement) ::Mmtrix::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start) return process_resultset(plan, adapter) if plan end end
# File lib/mmtrix/agent/database.rb, line 90 def get_connection(config, &connector) ConnectionManager.instance.get_connection(config, &connector) end
# File lib/mmtrix/agent/database.rb, line 238 def handle_exception_in_explain yield rescue => e begin # guarantees no throw from explain_sql ::Mmtrix::Agent.logger.error("Error getting query plan:", e) nil rescue # double exception. throw up your hands nil end end
# File lib/mmtrix/agent/database.rb, line 276 def is_select?(statement) parse_operation_from_query(statement) == 'select' end
# File lib/mmtrix/agent/database.rb, line 43 def obfuscate_sql(sql) Obfuscator.instance.obfuscator.call(sql) end
# File lib/mmtrix/agent/database.rb, line 280 def parameterized?(statement) Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/ end
# File lib/mmtrix/agent/database.rb, line 268 def parse_operation_from_query(sql) sql = sql.gsub(SQL_COMMENT_REGEX, EMPTY_STRING) if sql =~ /(\w+)/ op = $1.downcase return op if KNOWN_OPERATIONS.include?(op) end end
# File lib/mmtrix/agent/database.rb, line 196 def process_explain_results_mysql(results) return string_explain_plan_results(results) if results.is_a?(String) headers = [] values = [] if results.is_a?(Array) # We're probably using the jdbc-mysql gem for JRuby, which will give # us an array of hashes. headers = results.first.keys results.each do |row| values << headers.map { |h| row[h] } end else # We're probably using the native mysql driver gem, which will give us # a Mysql::Result object that responds to each_hash results.each_hash do |row| headers = row.keys values << headers.map { |h| row[h] } end end [headers, values] end
# File lib/mmtrix/agent/database.rb, line 218 def process_explain_results_mysql2(results) return string_explain_plan_results(results) if results.is_a?(String) headers = results.fields values = [] results.each { |row| values << row } [headers, values] end
# File lib/mmtrix/agent/database.rb, line 170 def process_explain_results_postgres(results) if results.is_a?(String) query_plan_string = results else lines = [] results.each { |row| lines << row[QUERY_PLAN] } query_plan_string = lines.join("\n") end unless record_sql_method == :raw query_plan_string = Mmtrix::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string) end values = query_plan_string.split("\n").map { |line| [line] } [[QUERY_PLAN], values] end
# File lib/mmtrix/agent/database.rb, line 228 def process_explain_results_sqlite(results) return string_explain_plan_results(results) if results.is_a?(String) headers = SQLITE_EXPLAIN_COLUMNS values = [] results.each do |row| values << headers.map { |h| row[h] } end [headers, values] end
# File lib/mmtrix/agent/database.rb, line 155 def process_resultset(results, adapter) case adapter.to_s when 'postgres', 'postgresql' process_explain_results_postgres(results) when 'mysql2' process_explain_results_mysql2(results) when 'mysql' process_explain_results_mysql(results) when 'sqlite' process_explain_results_sqlite(results) end end
# File lib/mmtrix/agent/database.rb, line 51 def record_sql_method(config_section=:transaction_tracer) key = record_sql_method_key(config_section) case Agent.config[key].to_s when 'off' :off when 'none' :off when 'false' :off when 'raw' :raw else :obfuscated end end
# File lib/mmtrix/agent/database.rb, line 68 def record_sql_method_key(config_section) case config_section when :transaction_tracer :'transaction_tracer.record_sql' when :slow_sql :'slow_sql.record_sql' else "#{config_section}.record_sql".to_sym end end
# File lib/mmtrix/agent/database.rb, line 47 def set_sql_obfuscator(type, &block) Obfuscator.instance.set_sql_obfuscator(type, &block) end
# File lib/mmtrix/agent/database.rb, line 85 def should_collect_explain_plans?(config_section=:transaction_tracer) should_record_sql?(config_section) && Agent.config["#{config_section}.explain_enabled".to_sym] end
# File lib/mmtrix/agent/database.rb, line 81 def should_record_sql?(config_section=:transaction_tracer) RECORD_FOR.include?(record_sql_method(config_section)) end
Sequel
returns explain plans as just one big pre-formatted String In that case, we send a nil headers array, and the single string wrapped in an array for the values. Note that we don’t use this method for Postgres explain plans, since they need to be passed through the explain plan obfuscator first.
# File lib/mmtrix/agent/database.rb, line 192 def string_explain_plan_results(results) [nil, [results]] end
# File lib/mmtrix/agent/database.rb, line 35 def truncate_query(query) if query.length > (MAX_QUERY_LENGTH - 4) query[0..MAX_QUERY_LENGTH - 4] + '...' else query end end