introspection/tracepoint_sequences.rb

tests/_examples/introspection/tracepoint_sequences.rb in the metorex repository.

# A TracePoint aimed at an instruction sequence hears the code the sequence
# runs, a `script_compiled` event reports the sequence of the code it
# compiled, and a trace's block runs with no frame between it and the code
# that fired the event.
class Ledger
  def total(rates)
    sum = rates.sum
    sum * 2
  end
end

sequence = RubyVM::InstructionSequence.of(Ledger.instance_method(:total))
heard = []
trace = TracePoint.new(:line) { |tp| heard << tp.lineno }
trace.enable(target: sequence)
Ledger.new.total([1, 2])
trace.disable
p(heard)

heard.clear
trace.enable(target: sequence, target_line: 8)
Ledger.new.total([1, 2])
trace.disable
p(heard)

compiled = []
TracePoint.new(:script_compiled) { |tp| compiled << tp.instruction_sequence }.enable do
  eval("def doubled(value)\n  [value].map { |held| held * 2 }\nend")
  eval("1", nil, "ledger_report.rb", 4)
end
compiled.each do |iseq|
  children = []
  iseq.each_child { |child| children << [child.label, child.first_lineno] }
  p([iseq.path.sub(__FILE__, "here"), iseq.absolute_path, iseq.label, iseq.first_lineno, children])
end

labels = nil
calls = TracePoint.new(:call) { |tp| labels ||= caller_locations(0, 3).map(&:label) }
calls.enable(target: Ledger.instance_method(:total)) { Ledger.new.total([1]) }
p(labels)

Output:

[7, 8]
[8]
["(eval at here:28)", nil, "<compiled>", 1, [["doubled", 1]]]
["ledger_report.rb", nil, "<compiled>", 4, []]
["block in <main>", "Ledger#total", "block in <main>"]