Class: ScoutApm::AutoInstrument::ParserImplementation::Rewriter

Inherits:
Parser::TreeRewriter
  • Object
show all
Defined in:
lib/scout_apm/auto_instrument/parser.rb

Instance Method Summary collapse

Constructor Details

#initializeRewriter



23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/scout_apm/auto_instrument/parser.rb', line 23

def initialize
  super

  # Keeps track of the parent - child relationship between nodes:
  @nesting = []

  # The stack of method nodes (type :def):
  @method = []

  # The stack of class nodes:
  @scope = []

  @cache = Cache.new
end

Instance Method Details

#instrument(source, file_name, line) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/scout_apm/auto_instrument/parser.rb', line 38

def instrument(source, file_name, line)
  # Don't log huge chunks of code... just the first line:
  if lines = source.lines and lines.count > 1
    source = lines.first.chomp + "..."
  end

  method_name = @method.last.children[0]
  bt = ["#{file_name}:#{line}:in `#{method_name}'"]

  return [
    "::ScoutApm::AutoInstrument("+ source.dump + ",#{bt}){",
    "}"
  ]
end

#on_and_asgn(node) ⇒ Object



85
86
87
# File 'lib/scout_apm/auto_instrument/parser.rb', line 85

def on_and_asgn(node)
  process(node.children[1])
end

#on_block(node) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
# File 'lib/scout_apm/auto_instrument/parser.rb', line 60

def on_block(node)
  # If we are not in a method, don't do any instrumentation:
  return if @method.empty?

  line = node.location.line || 'line?'
  column = node.location.column || 'column?' # not used
  method_name = node.children[0].children[1] || '*unknown*' # not used
  file_name = @source_rewriter.source_buffer.name

  wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
end

#on_hash(node) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/scout_apm/auto_instrument/parser.rb', line 111

def on_hash(node)
  node.children.each do |pair|
    # Skip `pair` if we're sure it's not using the hash shorthand syntax
    next if pair.type != :pair
    key_node, value_node = pair.children
    next unless key_node.type == :sym && value_node.type == :send
    key = key_node.children[0]
    next unless value_node.children.size == 2 && value_node.children[0].nil? && key == value_node.children[1]

    # Extract useful metadata for instrumentation:
    line = pair.location.line || 'line?'
    # column = pair.location.column || 'column?' # not used
    # method_name = key || '*unknown*' # not used
    file_name = @source_rewriter.source_buffer.name

    instrument_before, instrument_after = instrument(pair.location.expression.source, file_name, line)
    replace(pair.loc.expression, "#{key}: #{instrument_before}#{key}#{instrument_after}")
  end
  super
end

#on_mlhs(node) ⇒ Object



72
73
74
75
# File 'lib/scout_apm/auto_instrument/parser.rb', line 72

def on_mlhs(node)
  # Ignore / don't instrument multiple assignment (LHS).
  return
end

#on_op_asgn(node) ⇒ Object



77
78
79
# File 'lib/scout_apm/auto_instrument/parser.rb', line 77

def on_op_asgn(node)
  process(node.children[2])
end

#on_or_asgn(node) ⇒ Object



81
82
83
# File 'lib/scout_apm/auto_instrument/parser.rb', line 81

def on_or_asgn(node)
  process(node.children[1])
end

#on_send(node) ⇒ Object

Handle the method call AST node. If this method doesn’t call ‘super`, no futher rewriting is applied to children.



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/scout_apm/auto_instrument/parser.rb', line 90

def on_send(node)
  # We aren't interested in top level function calls:
  return if @method.empty?

  if @cache.local_assignments?(node)
    return super
  end

  # This ignores both initial block method invocation `*x*{}`, and subsequent nested invocations `x{*y*}`:
  return if parent_type?(:block)

  # Extract useful metadata for instrumentation:
  line = node.location.line || 'line?'
  column = node.location.column || 'column?' # not used
  method_name = node.children[1] || '*unknown*' # not used
  file_name = @source_rewriter.source_buffer.name

  # Wrap the expression with instrumentation:
  wrap(node.location.expression, *instrument(node.location.expression.source, file_name, line))
end

#parent_type?(type, up = 1) ⇒ Boolean

Look up 1 or more nodes to check if the parent exists and matches the given type.



56
57
58
# File 'lib/scout_apm/auto_instrument/parser.rb', line 56

def parent_type?(type, up = 1)
  parent = @nesting[@nesting.size - up - 1] and parent.type == type
end

#process(node) ⇒ Object

Invoked for every AST node as it is processed top to bottom.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/scout_apm/auto_instrument/parser.rb', line 133

def process(node)
  # We are nesting inside this node:
  @nesting.push(node)

  if node and node.type == :def
    # If the node is a method, push it on the method stack as well:
    @method.push(node)
    super
    @method.pop
  elsif node and node.type == :class
    @scope.push(node.children[0])
    super
    @scope.pop
  else
    super
  end

  @nesting.pop
end