require "trace_tree/version" require 'binding_of_callers/pry' require 'trace_tree/point' require 'trace_tree/short_gem_path' require 'trace_tree/color' require 'trace_tree/return_value' require 'trace_tree/args' require 'trace_tree/tmp_file' require 'trace_tree/timer' require 'trace_tree/config' require 'thread' require 'terminal-tableofhashes' class Binding def trace_tree *log, **opt, &to_do TraceTree.new(self).generate *log, **opt, &to_do end end class TraceTree MainFile = __FILE__ Events = [:b_call, :b_return, :c_call, :c_return, :call, :return, :class, :end, :thread_begin, :thread_end] def initialize bi @bi = bi @trace_points = Queue.new @timer = Timer.new @config = Config.load end def generate *log, **opt, &to_do @opt = opt @log = dump_location *log @debug = TmpFile.new opt[:debug] if opt[:debug] enhance_point @build_command = (opt[:html] || opt[:htmp]) ? :tree_html_full : :tree_graph make_filter @__file__, @__line__, there = bi.eval('[__FILE__, __LINE__, self]') #start_trace timer[:trace] @tp = TracePoint.new *Events, &@deal @tp.enable there.instance_eval &to_do ensure #stop_trace return unless @tp @tp.disable timer[:trace] dump_trace_tree end private attr_reader :bi, :trace_points, :log, :build_command, :timer, :opt, :point_loader, :config def dump_location *log return TmpFile.new opt[:tmp] if opt[:tmp] return TmpFile.new(opt[:htmp] + '.html') if opt[:htmp] log.empty? ? STDOUT : log[0] end def enhance_point enhancement = [] enhancement << TraceTree::Color unless opt[:color] == false enhancement << TraceTree::ShortGemPath unless opt[:gem] == false if opt[:return].nil? || opt[:return] == true enhancement << TraceTree::ConsoleReturnValue elsif opt[:return] == :lux enhancement << TraceTree::LuxuryReturnValue end enhancement << TraceTree::Args if opt[:args] == true @point_loader = Point::Loader.new *enhancement, config end def dump_trace_tree timer[:tree] tree = sort(trace_points_array).send build_command timer[:tree] @debug.puts table_of_points if defined? @debug log.puts tree log.puts timer.to_s if opt[:timer] rescue => e log.puts timer.to_s, e.inspect, e.backtrace, table_of_points end def table_of_points Terminal::Table.from_hashes trace_points_array.map(&:to_h) end def make_filter filters = [] if opt.key?(:in) @in = Array(opt[:in] || //) filters << '@in.any?{ |pattern| pattern =~ point.path }' end if opt.key?(:out) @out = Array(opt[:out]) filters << '@out.all?{ |pattern| pattern !~ point.path }' end if opt.key?(:no_methods) @no_methods = Array(opt[:no_methods]) filters << 'outside_hidden_stack?(point)' end if filters.empty? return @deal = -> point { trace_points << point_loader.create(point) } end self.singleton_class.class_eval <<-EOM def wanted? point return false if point.end_of_trace? return true if native?(point) || point.thread_relative? || point.method_defined_by_define_method? #{filters.join(' && ')} end EOM @deal = -> point do po = point_loader.create(point) trace_points << po if wanted? po end end def outside_hidden_stack? point stack = (point.thread[:trace_tree_no_methods_stack] ||= []) empty = stack.empty? if @no_methods.any?{ |pattern| pattern =~ point.method_name } if !empty && point.terminate?(stack.last) stack.pop else stack << point point << Point::Omitted.new end return true end empty end def native? point MainFile == point.path || (:b_call == point.event && @__file__ == point.path && @__line__ == point.lineno) end def sort trace_points stacks = Hash.new{|h, thread| h[thread] = []} initialized_threads, began_threads = {}, {} trace_points.each do |point| stack = stacks[point.thread] unless stack.empty? if point.terminate? stack.last stack.last.terminal = point stack.pop else stack.last << point stack << point end else stack << point end initialized_threads[point.return_value] = point if Point::CreturnThreadInitialize.class_of? point began_threads[point.thread] = point if Point::Threadbegin.class_of? point end initialized_threads.each do |thread, point| point.thread_begin = began_threads[thread] end #binding.pry stacks[trace_points.first.thread][0]. callees[0]. callees[0] end def trace_points_array return @tpa if defined? @tpa @tpa = [] @tpa << trace_points.deq until trace_points.size == 0 @tpa end end