Module: OneApm::Agent::Database

Extended by:
Database
Included in:
Database
Defined in:
lib/one_apm/agent/database.rb,
lib/one_apm/agent/database/obfuscator.rb,
lib/one_apm/agent/database/obfuscation_helpers.rb,
lib/one_apm/agent/database/postgres_explain_obfuscator.rb

Defined Under Namespace

Modules: ObfuscationHelpers, PostgresExplainObfuscator Classes: ConnectionManager, Obfuscator, Statement

Constant Summary collapse

OA_MAX_QUERY_LENGTH =
16384
OA_RECORD_FOR =
[:raw, :obfuscated].freeze
OA_SUPPORTED_ADAPTERS_FOR_EXPLAIN =
%w[postgres postgresql mysql2 mysql sqlite].freeze
OA_QUERY_PLAN =
'QUERY PLAN'.freeze
OA_SQLITE_EXPLAIN_COLUMNS =
%w[addr opcode p1 p2 p3 p4 p5 comment]
OA_KNOWN_OPERATIONS =
[
  'alter',
  'select',
  'update',
  'delete',
  'insert',
  'create',
  'show',
  'set',
  'exec',
  'execute',
  'call'
]
OA_SQL_COMMENT_REGEX =
Regexp.new('/\*.*?\*/', Regexp::MULTILINE).freeze

Instance Method Summary collapse

Instance Method Details

#adapter_from_config(config) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/one_apm/agent/database.rb', line 82

def adapter_from_config(config)
  if config[:adapter]
    return config[:adapter].to_s
  elsif config[:uri] && config[:uri].to_s =~ /^jdbc:([^:]+):/
    return $1
  end
end

#capture_query(query) ⇒ Object



28
29
30
# File 'lib/one_apm/agent/database.rb', line 28

def capture_query(query)
  Helper.correctly_encoded(truncate_query(query))
end

#close_connectionsObject



78
79
80
# File 'lib/one_apm/agent/database.rb', line 78

def close_connections
  ConnectionManager.instance.close_connections
end

#explain_sql(sql, connection_config, &explainer) ⇒ Object



90
91
92
93
94
95
# File 'lib/one_apm/agent/database.rb', line 90

def explain_sql(sql, connection_config, &explainer)
  return nil unless sql && connection_config
  statement = sql.split(";\n")[0] # only explain the first
  explain_plan = explain_statement(statement, connection_config, &explainer)
  return explain_plan || []
end

#explain_statement(statement, config, &explainer) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/one_apm/agent/database.rb', line 99

def explain_statement(statement, config, &explainer)
  return unless is_select?(statement)

  if statement[-3,3] == '...'
    OneApm::Manager.logger.debug('Unable to collect explain plan for truncated query.')
    return
  end

  if parameterized?(statement)
    OneApm::Manager.logger.debug('Unable to collect explain plan for parameterized query.')
    return
  end

  adapter = adapter_from_config(config)
  if !OA_SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
    OneApm::Manager.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)
    OneApm::Manager.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
    return process_resultset(plan, adapter) if plan
  end
end

#get_connection(config, &connector) ⇒ Object



74
75
76
# File 'lib/one_apm/agent/database.rb', line 74

def get_connection(config, &connector)
  ConnectionManager.instance.get_connection(config, &connector)
end

#handle_exception_in_explainObject



200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/one_apm/agent/database.rb', line 200

def handle_exception_in_explain
  yield
rescue => e
  begin
    # guarantees no throw from explain_sql
    OneApm::Manager.logger.error("Error getting query plan:", e)
    nil
  rescue
    # double exception. throw up your hands
    nil
  end
end

#is_select?(statement) ⇒ Boolean

Returns:

  • (Boolean)


237
238
239
# File 'lib/one_apm/agent/database.rb', line 237

def is_select?(statement)
  parse_operation_from_query(statement) == 'select'
end

#obfuscate_sql(sql) ⇒ Object



40
41
42
# File 'lib/one_apm/agent/database.rb', line 40

def obfuscate_sql(sql)
  Obfuscator.instance.obfuscator.call(sql)
end

#parameterized?(statement) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/one_apm/agent/database.rb', line 241

def parameterized?(statement)
  Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/
end

#parse_operation_from_query(sql) ⇒ Object



229
230
231
232
233
234
235
# File 'lib/one_apm/agent/database.rb', line 229

def parse_operation_from_query(sql)
  sql = sql.gsub(OA_SQL_COMMENT_REGEX, '')
  if sql =~ /(\w+)/
    op = $1.downcase
    return op if OA_KNOWN_OPERATIONS.include?(op)
  end
end

#process_explain_results_mysql(results) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/one_apm/agent/database.rb', line 162

def process_explain_results_mysql(results)
  return string_explain_plan_results(results) if results.is_a?(String)
  headers = []
  values  = []
  if results.is_a?(Array)
    headers = results.first.keys
    results.each do |row|
      values << headers.map { |h| row[h] }
    end
  else
    results.each_hash do |row|
      headers = row.keys
      values << headers.map { |h| row[h] }
    end
  end
  [headers, values]
end

#process_explain_results_mysql2(results) ⇒ Object



180
181
182
183
184
185
186
# File 'lib/one_apm/agent/database.rb', line 180

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

#process_explain_results_postgres(results) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/one_apm/agent/database.rb', line 141

def process_explain_results_postgres(results)
  if results.is_a?(String)
    query_plan_string = results
  else
    lines = []
    results.each { |row| lines << row[OA_QUERY_PLAN] }
    query_plan_string = lines.join("\n")
  end

  unless record_sql_method == :raw
    query_plan_string = OneApm::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
  end
  values = query_plan_string.split("\n").map { |line| [line] }

  [[OA_QUERY_PLAN], values]
end

#process_explain_results_sqlite(results) ⇒ Object



190
191
192
193
194
195
196
197
198
# File 'lib/one_apm/agent/database.rb', line 190

def process_explain_results_sqlite(results)
  return string_explain_plan_results(results) if results.is_a?(String)
  headers = OA_SQLITE_EXPLAIN_COLUMNS
  values  = []
  results.each do |row|
    values << headers.map { |h| row[h] }
  end
  [headers, values]
end

#process_resultset(results, adapter) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/one_apm/agent/database.rb', line 126

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

#record_sql_method(config_section = :transaction_tracer) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/one_apm/agent/database.rb', line 48

def record_sql_method(config_section=:transaction_tracer)
  case OneApm::Manager.config["#{config_section}.record_sql".to_sym].to_s
  when 'off'
    :off
  when 'none'
    :off
  when 'false'
    :off
  when 'raw'
    :raw
  else
    :obfuscated
  end
end

#set_sql_obfuscator(type, &block) ⇒ Object



44
45
46
# File 'lib/one_apm/agent/database.rb', line 44

def set_sql_obfuscator(type, &block)
  Obfuscator.instance.set_sql_obfuscator(type, &block)
end

#should_collect_explain_plans?(config_section = :transaction_tracer) ⇒ Boolean

Returns:

  • (Boolean)


69
70
71
72
# File 'lib/one_apm/agent/database.rb', line 69

def should_collect_explain_plans?(config_section=:transaction_tracer)
  should_record_sql?(config_section) &&
    OneApm::Manager.config["#{config_section}.explain_enabled".to_sym]
end

#should_record_sql?(config_section = :transaction_tracer) ⇒ Boolean

Returns:

  • (Boolean)


65
66
67
# File 'lib/one_apm/agent/database.rb', line 65

def should_record_sql?(config_section=:transaction_tracer)
  OA_RECORD_FOR.include?(record_sql_method(config_section))
end

#string_explain_plan_results(results) ⇒ Object



158
159
160
# File 'lib/one_apm/agent/database.rb', line 158

def string_explain_plan_results(results)
  [nil, [results]]
end

#truncate_query(query) ⇒ Object



32
33
34
35
36
37
38
# File 'lib/one_apm/agent/database.rb', line 32

def truncate_query(query)
  if query.length > (OA_MAX_QUERY_LENGTH - 4)
    query[0..OA_MAX_QUERY_LENGTH - 4] + '...'
  else
    query
  end
end