module NewRelic::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/new_relic/agent/database.rb, line 99 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/new_relic/agent/database.rb, line 30 def capture_query(query) Helper.correctly_encoded(truncate_query(query)) end
# File lib/new_relic/agent/database.rb, line 93 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 Relic
# File lib/new_relic/agent/database.rb, line 118 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/new_relic/agent/database.rb, line 127 def explain_statement(statement, config, explainer) return unless explainer && is_select?(statement) if statement[-3,3] == '...' NewRelic::Agent.logger.debug('Unable to collect explain plan for truncated query.') return end if parameterized?(statement) NewRelic::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) NewRelic::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) ::NewRelic::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start) return process_resultset(plan, adapter) if plan end end
# File lib/new_relic/agent/database.rb, line 89 def get_connection(config, &connector) ConnectionManager.instance.get_connection(config, &connector) end
# File lib/new_relic/agent/database.rb, line 237 def handle_exception_in_explain yield rescue => e begin # guarantees no throw from explain_sql ::NewRelic::Agent.logger.error("Error getting query plan:", e) nil rescue # double exception. throw up your hands nil end end
# File lib/new_relic/agent/database.rb, line 275 def is_select?(statement) parse_operation_from_query(statement) == 'select' end
# File lib/new_relic/agent/database.rb, line 42 def obfuscate_sql(sql) Obfuscator.instance.obfuscator.call(sql) end
# File lib/new_relic/agent/database.rb, line 279 def parameterized?(statement) Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/ end
# File lib/new_relic/agent/database.rb, line 267 def parse_operation_from_query(sql) sql = Helper.correctly_encoded(sql).gsub(SQL_COMMENT_REGEX, EMPTY_STRING) if sql =~ /(\w+)/ op = $1.downcase return op if KNOWN_OPERATIONS.include?(op) end end
# File lib/new_relic/agent/database.rb, line 195 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/new_relic/agent/database.rb, line 217 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/new_relic/agent/database.rb, line 169 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 = NewRelic::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string) end values = query_plan_string.split("\n").map { |line| [line] } [[QUERY_PLAN], values] end
# File lib/new_relic/agent/database.rb, line 227 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/new_relic/agent/database.rb, line 154 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/new_relic/agent/database.rb, line 50 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/new_relic/agent/database.rb, line 67 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/new_relic/agent/database.rb, line 46 def set_sql_obfuscator(type, &block) Obfuscator.instance.set_sql_obfuscator(type, &block) end
# File lib/new_relic/agent/database.rb, line 84 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/new_relic/agent/database.rb, line 80 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/new_relic/agent/database.rb, line 191 def string_explain_plan_results(results) [nil, [results]] end
# File lib/new_relic/agent/database.rb, line 34 def truncate_query(query) if query.length > (MAX_QUERY_LENGTH - 4) query[0..MAX_QUERY_LENGTH - 4] + '...' else query end end