class ScoutApm::Utils::ActiveRecordMetricName

Constants

BEGIN_STATEMENT
COMMIT
COUNT
COUNT_LABEL
DEFAULT_METRIC
DELETE_LABEL
DELETE_REGEX
FROM
INSERT_LABEL
INSERT_REGEX
INTO
NON_GREEDY_CONSUME
REGEX_OPERATION
SELECT_LABEL
SELECT_REGEX
TABLE
UNKNOWN_LABEL
UPDATE_LABEL
UPDATE_REGEX
WHITE_SPACE

Regex based naming #

Attributes

name[R]
sql[R]

Public Class Methods

new(sql, name) click to toggle source
# File lib/scout_apm/utils/active_record_metric_name.rb, line 7
def initialize(sql, name)
  @sql = sql || ""
  @name = name.to_s
end

Public Instance Methods

==(o)
Alias for: eql?
eql?(o) click to toggle source

For the layer lookup. Reminder: eql? is for Hash equality: returns true if obj and other refer to the same hash key.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 48
def eql?(o)
  self.class    == o.class &&
  name.downcase == o.name.downcase
end
Also aliased as: ==
hash() click to toggle source

For the layer lookup.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 41
def hash
  h = name.downcase.hash
  h
end
model() click to toggle source

This only returns a value if a name is provided via initialize.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 31
def model
  parts.first
end
normalized_operation() click to toggle source

This only returns a value if a name is provided via initialize.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 36
def normalized_operation
  parse_operation
end
to_s() click to toggle source

Converts an SQL string and the name (typically assigned automatically by rails) into a Scout metric_name.

This prefers to use the ActiveRecord-provided name over parsing SQL as parsing is slower.

sql: SELECT “places”.* FROM “places” ORDER BY “places”.“position” ASC name: Place Load metric_name: Place/find

# File lib/scout_apm/utils/active_record_metric_name.rb, line 20
def to_s
  return @to_s if @to_s
  parsed = parse_operation
  if parsed
    @to_s = "#{model}/#{parsed}"
  else
    @to_s = regex_name(sql)
  end
end

Private Instance Methods

operation() click to toggle source

This only returns a value if a name is provided via initialize.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 58
def operation
  if parts.length >= 2
    parts[1].downcase
  end
end
parse_operation() click to toggle source

Returns nil if no match Returns nil if the operation wasn't under developer control (and hence isn't interesting to report)

# File lib/scout_apm/utils/active_record_metric_name.rb, line 71
def parse_operation
  case operation
  when 'indexes', 'columns' then nil # not under developer control
  when 'load' then 'find'
  when 'destroy', 'find', 'save', 'create', 'exists' then operation
  when 'update' then 'save'
  else
    if model == 'Join'
      operation
    end
  end
end
parts() click to toggle source

This only returns a value if a name is provided via initialize.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 65
def parts
  name.split(" ")
end
regex_name(sql) click to toggle source

Attempt to do some basic parsing of SQL via regexes to extract the SQL verb (select, update, etc) and the table being operated on.

This is a fallback from what ActiveRecord gives us, we prefer its names. But sometimes it is giving us a no-name query, and we have to attempt to figure it out ourselves.

This relies on ActiveSupport's classify method. If it's not present, just skip the attempt to rename here. This could happen in a Grape or Sinatra application that doesn't import ActiveSupport. At this point, you're already using ActiveRecord, so it's likely loaded anyway.

# File lib/scout_apm/utils/active_record_metric_name.rb, line 122
def regex_name(sql)
  # We rely on the ActiveSupport inflections code here. Bail early if we can't use it.
  return UNKNOWN_LABEL unless UNKNOWN_LABEL.respond_to?(:classify)

  if match = SELECT_REGEX.match(sql)
    operation =
      if match[2]
        COUNT_LABEL
      else
        SELECT_LABEL
      end
    "#{match[3].gsub(/\W/,'').classify}/#{operation}"
  elsif match = UPDATE_REGEX.match(sql)
    "#{match[2].classify}/#{UPDATE_LABEL}"
  elsif match = INSERT_REGEX.match(sql)
    "#{match[2].classify}/#{INSERT_LABEL}"
  elsif match = DELETE_REGEX.match(sql)
    "#{match[2].classify}/#{DELETE_LABEL}"
  elsif sql == BEGIN_STATEMENT
    "SQL/#{BEGIN_STATEMENT.downcase}"
  elsif sql == COMMIT
    "SQL/#{COMMIT.downcase}"
  else
    UNKNOWN_LABEL
  end
rescue
  UNKNOWN_LABEL
end