codebeacon-tracer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
 - data/lib/codebeacon/tracer/src/configuration.rb +199 -0
 - data/lib/codebeacon/tracer/src/data/database.rb +54 -0
 - data/lib/codebeacon/tracer/src/data/metadata_mapper.rb +26 -0
 - data/lib/codebeacon/tracer/src/data/node_source_mapper.rb +29 -0
 - data/lib/codebeacon/tracer/src/data/persistence_manager.rb +107 -0
 - data/lib/codebeacon/tracer/src/data/tree_node_mapper.rb +60 -0
 - data/lib/codebeacon/tracer/src/logger.rb +58 -0
 - data/lib/codebeacon/tracer/src/models/call_tree.rb +54 -0
 - data/lib/codebeacon/tracer/src/models/node_builder.rb +60 -0
 - data/lib/codebeacon/tracer/src/models/node_source.rb +32 -0
 - data/lib/codebeacon/tracer/src/models/thread_local_call_tree_manager.rb +35 -0
 - data/lib/codebeacon/tracer/src/models/tpklass.rb +53 -0
 - data/lib/codebeacon/tracer/src/models/tree_node.rb +113 -0
 - data/lib/codebeacon/tracer/src/rails/middleware.rb +47 -0
 - data/lib/codebeacon/tracer/src/rails/railtie.rb +10 -0
 - data/lib/codebeacon/tracer/src/tracer.rb +113 -0
 - data/lib/codebeacon/tracer/version.rb +7 -0
 - data/lib/codebeacon-tracer.rb +132 -0
 - metadata +193 -0
 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'call_tree'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 5 
     | 
    
         
            +
                class ThreadLocalCallTreeManager
         
     | 
| 
      
 6 
     | 
    
         
            +
                  attr_reader :trees, :trace_id
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def initialize(trace_id)
         
     | 
| 
      
 9 
     | 
    
         
            +
                    @trees = []
         
     | 
| 
      
 10 
     | 
    
         
            +
                    @trace_id = trace_id
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def current()
         
     | 
| 
      
 14 
     | 
    
         
            +
                    Thread.current[thread_key] ||= begin
         
     | 
| 
      
 15 
     | 
    
         
            +
                      CallTree.new(Thread.current).tap do |tree|
         
     | 
| 
      
 16 
     | 
    
         
            +
                        Codebeacon::Tracer.logger.debug("Creating new call tree for thread: #{Thread.current} with trace_id: #{trace_id}")
         
     | 
| 
      
 17 
     | 
    
         
            +
                        @trees << tree
         
     | 
| 
      
 18 
     | 
    
         
            +
                      end
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def cleanup()
         
     | 
| 
      
 23 
     | 
    
         
            +
                    Thread.list.each do |thread|
         
     | 
| 
      
 24 
     | 
    
         
            +
                      if thread[thread_key]
         
     | 
| 
      
 25 
     | 
    
         
            +
                        thread[thread_key] = nil
         
     | 
| 
      
 26 
     | 
    
         
            +
                      end
         
     | 
| 
      
 27 
     | 
    
         
            +
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  private def thread_key()
         
     | 
| 
      
 31 
     | 
    
         
            +
                    "call_tree_#{trace_id}".to_sym
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 3 
     | 
    
         
            +
                class TPKlass
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(tp)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @tp = tp
         
     | 
| 
      
 6 
     | 
    
         
            +
                  end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  def tp_class
         
     | 
| 
      
 9 
     | 
    
         
            +
                    klass = @tp.self.class
         
     | 
| 
      
 10 
     | 
    
         
            +
                    while klass && klass.to_s =~ /Class:0x/
         
     | 
| 
      
 11 
     | 
    
         
            +
                      klass = klass.superclass
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                    klass
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  def defined_class
         
     | 
| 
      
 17 
     | 
    
         
            +
                    klass = @tp.defined_class.to_s.sub("#<", "").sub(">", "")
         
     | 
| 
      
 18 
     | 
    
         
            +
                    if klass.match(/^(Class|Module):/)
         
     | 
| 
      
 19 
     | 
    
         
            +
                      klass = klass.split(":")[1..].join(":")
         
     | 
| 
      
 20 
     | 
    
         
            +
                    elsif klass.match(/:0x[0-9a-f]+$/)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      klass = klass.split(":")[0..-2].join(":")
         
     | 
| 
      
 22 
     | 
    
         
            +
                      klass += " Singleton"
         
     | 
| 
      
 23 
     | 
    
         
            +
                    end
         
     | 
| 
      
 24 
     | 
    
         
            +
                    klass
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def tp_class_name
         
     | 
| 
      
 28 
     | 
    
         
            +
                    if @tp.self.is_a?(Module)
         
     | 
| 
      
 29 
     | 
    
         
            +
                      @tp.self.name
         
     | 
| 
      
 30 
     | 
    
         
            +
                    else
         
     | 
| 
      
 31 
     | 
    
         
            +
                      klass = @tp.self.class
         
     | 
| 
      
 32 
     | 
    
         
            +
                      while klass && klass.to_s =~ /Class:0x/
         
     | 
| 
      
 33 
     | 
    
         
            +
                        klass = klass.superclass
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                      klass.name
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def type
         
     | 
| 
      
 40 
     | 
    
         
            +
                    case @tp.self
         
     | 
| 
      
 41 
     | 
    
         
            +
                    when Class
         
     | 
| 
      
 42 
     | 
    
         
            +
                      :Class
         
     | 
| 
      
 43 
     | 
    
         
            +
                    when Module
         
     | 
| 
      
 44 
     | 
    
         
            +
                      :Module
         
     | 
| 
      
 45 
     | 
    
         
            +
                    when Object
         
     | 
| 
      
 46 
     | 
    
         
            +
                      :Object
         
     | 
| 
      
 47 
     | 
    
         
            +
                    else
         
     | 
| 
      
 48 
     | 
    
         
            +
                      :Unknown
         
     | 
| 
      
 49 
     | 
    
         
            +
                    end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
              end
         
     | 
| 
      
 53 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,113 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'set'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              # store node memory in a format that can be directly loaded 1:1 into sqlite3
         
     | 
| 
      
 4 
     | 
    
         
            +
              # could use rocksdb or something else
         
     | 
| 
      
 5 
     | 
    
         
            +
              # could use lmdb
         
     | 
| 
      
 6 
     | 
    
         
            +
              # could use leveldb
         
     | 
| 
      
 7 
     | 
    
         
            +
              # actually log if this speeds up developer time - would be really cool to show!
         
     | 
| 
      
 8 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 9 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 10 
     | 
    
         
            +
                class TreeNode
         
     | 
| 
      
 11 
     | 
    
         
            +
                  class TraceStatus < Struct.new(:previous_line)
         
     | 
| 
      
 12 
     | 
    
         
            +
                    def any_lines_traced?
         
     | 
| 
      
 13 
     | 
    
         
            +
                      !previous_line.nil?
         
     | 
| 
      
 14 
     | 
    
         
            +
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  # attr_accessor :file, :line, :method, :depth, :caller, :gem_entry, :children, :parent, :block, :locals, :ast, :return_value, :linevars
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  # def initialize(file: nil, line: nil, method: nil, depth: 0, caller: "", gem_entry: false, parent: nil, block: false, locals: [], ast: nil, return_value: "")
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #   @file = file
         
     | 
| 
      
 20 
     | 
    
         
            +
                  #   @line = line
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #   @method = method
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #   @depth = depth
         
     | 
| 
      
 23 
     | 
    
         
            +
                  #   @caller = caller
         
     | 
| 
      
 24 
     | 
    
         
            +
                  #   @gem_entry = gem_entry
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #   @parent = parent
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #   @block = block
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #   @locals = locals
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #   @ast = ast
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #   @return_value = return_value
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #   @linevars = {}
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #   @children = []
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  attr_accessor :file, :line, :method, :object_id, :tp_class, :tp_defined_class, :tp_class_name, :self_type, :depth, :caller, :gem_entry, :children, :parent, :block, :locals, :return_value, :linevars, :node_source, :trace_status, :script, :backtrace_count, :backtrace_location, :script_binding, :script_self
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def initialize(file: nil, line: nil, object_id: nil, method: nil, tp_class: nil, tp_defined_class: nil, tp_class_name: nil, self_type: nil, depth: 0, caller: "", gem_entry: false, parent: nil, block: false, locals: [], return_value: nil, node_source: nil, script: false)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @file = file
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @line = line
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @method = method
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @object_id = object_id
         
     | 
| 
      
 41 
     | 
    
         
            +
                    @tp_class = tp_class
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @tp_defined_class = tp_defined_class
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @tp_class_name = tp_class_name
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @self_type = self_type
         
     | 
| 
      
 45 
     | 
    
         
            +
                    @children = []
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @depth = depth
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @gem_entry = gem_entry
         
     | 
| 
      
 48 
     | 
    
         
            +
                    @caller = caller
         
     | 
| 
      
 49 
     | 
    
         
            +
                    @parent = parent
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @block = block
         
     | 
| 
      
 51 
     | 
    
         
            +
                    @locals = locals
         
     | 
| 
      
 52 
     | 
    
         
            +
                    @return_value = return_value
         
     | 
| 
      
 53 
     | 
    
         
            +
                    @linevars = Hash.new { |h, k| h[k] = {} }
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @node_source = node_source
         
     | 
| 
      
 55 
     | 
    
         
            +
                    @trace_status = TraceStatus.new(nil)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @script = script
         
     | 
| 
      
 57 
     | 
    
         
            +
                    @backtrace_count = 0
         
     | 
| 
      
 58 
     | 
    
         
            +
                    @backtrace_location = nil
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @script_binding = nil
         
     | 
| 
      
 60 
     | 
    
         
            +
                    @script_self = nil
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def add_line(lineno, variables)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @linevars[lineno] = @linevars[lineno].merge(variables)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                  def set_args(lineno, variables)
         
     | 
| 
      
 68 
     | 
    
         
            +
                    @linevars[lineno] = @linevars[lineno].merge(variables)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def inspect
         
     | 
| 
      
 72 
     | 
    
         
            +
                    ivar_inspect = instance_variables.reject { |ivar| [:@children].include?(ivar) }.map do |ivar|
         
     | 
| 
      
 73 
     | 
    
         
            +
                      "#{ivar.to_s}=#{instance_variable_get(ivar).inspect}"
         
     | 
| 
      
 74 
     | 
    
         
            +
                    end
         
     | 
| 
      
 75 
     | 
    
         
            +
                    ivar_inspect << "@children=#<#{@children.map(&:to_s)}>"
         
     | 
| 
      
 76 
     | 
    
         
            +
                    "#<#{self.class.name}:0x#{self.object_id.to_s} #{ivar_inspect.join(', ')}>"
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def inspect_tree(attrs = [], depth = 0)
         
     | 
| 
      
 80 
     | 
    
         
            +
                    str = file.split("/").last + ":#{script ? "script" : method}"
         
     | 
| 
      
 81 
     | 
    
         
            +
                    if !attrs.empty?
         
     | 
| 
      
 82 
     | 
    
         
            +
                      attr_values = attrs.map { |k, v| "#{k}=#{v.inspect}" }.join(', ')
         
     | 
| 
      
 83 
     | 
    
         
            +
                      str += " " + attrs
         
     | 
| 
      
 84 
     | 
    
         
            +
                    end
         
     | 
| 
      
 85 
     | 
    
         
            +
                    str += children.map { |c| "\n" + " " * (depth + 1) * 2 + c.inspect_tree(attrs, depth + 1) }.join()
         
     | 
| 
      
 86 
     | 
    
         
            +
                    return str
         
     | 
| 
      
 87 
     | 
    
         
            +
                  end
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                  def to_h
         
     | 
| 
      
 90 
     | 
    
         
            +
                    children = depth > Codebeacon::Tracer.config.max_depth ? nil : @children.map(&:to_h)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    is_truncated = depth > Codebeacon::Tracer.config.max_depth ? true : false
         
     | 
| 
      
 92 
     | 
    
         
            +
                    {
         
     | 
| 
      
 93 
     | 
    
         
            +
                      file: @file,
         
     | 
| 
      
 94 
     | 
    
         
            +
                      line: @line,
         
     | 
| 
      
 95 
     | 
    
         
            +
                      method: @method,
         
     | 
| 
      
 96 
     | 
    
         
            +
                      class: @tp_class,
         
     | 
| 
      
 97 
     | 
    
         
            +
                      tp_defined_class: @tp_defined_class,
         
     | 
| 
      
 98 
     | 
    
         
            +
                      tp_class_name: @tp_class_name,
         
     | 
| 
      
 99 
     | 
    
         
            +
                      class_name: @class_name,
         
     | 
| 
      
 100 
     | 
    
         
            +
                      self_type: @self_type,
         
     | 
| 
      
 101 
     | 
    
         
            +
                      gemEntry: @gem_entry,
         
     | 
| 
      
 102 
     | 
    
         
            +
                      caller: @caller,
         
     | 
| 
      
 103 
     | 
    
         
            +
                      isDepthTruncated: is_truncated,
         
     | 
| 
      
 104 
     | 
    
         
            +
                      children: children
         
     | 
| 
      
 105 
     | 
    
         
            +
                    }
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  def depth_truncated?
         
     | 
| 
      
 109 
     | 
    
         
            +
                    @depth > Codebeacon::Tracer.config.max_depth && @children.count > 0
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,47 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 2 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 3 
     | 
    
         
            +
                class Middleware
         
     | 
| 
      
 4 
     | 
    
         
            +
                  def initialize(app)
         
     | 
| 
      
 5 
     | 
    
         
            +
                    @app = app
         
     | 
| 
      
 6 
     | 
    
         
            +
                    @first_run = true
         
     | 
| 
      
 7 
     | 
    
         
            +
                  end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def call(env)
         
     | 
| 
      
 10 
     | 
    
         
            +
                    response = nil
         
     | 
| 
      
 11 
     | 
    
         
            +
                    # tracer = Tracer.news
         
     | 
| 
      
 12 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 13 
     | 
    
         
            +
                      Codebeacon::Tracer.config.set_query_config(env['QUERY_STRING'])
         
     | 
| 
      
 14 
     | 
    
         
            +
                      if !@first_run #Codebeacon::Tracer.config.trace_enabled? && !@first_run
         
     | 
| 
      
 15 
     | 
    
         
            +
                        Codebeacon::Tracer.trace do |tracer|
         
     | 
| 
      
 16 
     | 
    
         
            +
                          dry_run_log = Codebeacon::Tracer.config.dry_run? ? "--DRY RUN-- " : ""
         
     | 
| 
      
 17 
     | 
    
         
            +
                          Codebeacon::Tracer.logger.info(dry_run_log + "Tracing enabled for URI=#{env['REQUEST_URI']}")
         
     | 
| 
      
 18 
     | 
    
         
            +
                          response = @app.call(env).tap do |_|
         
     | 
| 
      
 19 
     | 
    
         
            +
                            Codebeacon::Tracer.logger.info("Tracing disabled for URI=#{env['REQUEST_URI']}")
         
     | 
| 
      
 20 
     | 
    
         
            +
                          end
         
     | 
| 
      
 21 
     | 
    
         
            +
                          begin
         
     | 
| 
      
 22 
     | 
    
         
            +
                            params = env['action_dispatch.request.parameters'].dup
         
     | 
| 
      
 23 
     | 
    
         
            +
                            tracer.name = "#{params.delete('controller')}##{params.delete('action')}"
         
     | 
| 
      
 24 
     | 
    
         
            +
                            tracer.description = params.to_json
         
     | 
| 
      
 25 
     | 
    
         
            +
                          rescue => e
         
     | 
| 
      
 26 
     | 
    
         
            +
                            Codebeacon::Tracer.logger.error("Error setting tracer metadata: #{e.message}")
         
     | 
| 
      
 27 
     | 
    
         
            +
                          end
         
     | 
| 
      
 28 
     | 
    
         
            +
                          response
         
     | 
| 
      
 29 
     | 
    
         
            +
                        end
         
     | 
| 
      
 30 
     | 
    
         
            +
                      else
         
     | 
| 
      
 31 
     | 
    
         
            +
                        if Codebeacon::Tracer.config.trace_enabled? && @first_run
         
     | 
| 
      
 32 
     | 
    
         
            +
                          Codebeacon::Tracer.logger.info("Bypassing first request for performance.")
         
     | 
| 
      
 33 
     | 
    
         
            +
                        end
         
     | 
| 
      
 34 
     | 
    
         
            +
                        @first_run = false if @first_run
         
     | 
| 
      
 35 
     | 
    
         
            +
                        response = @app.call(env)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      end
         
     | 
| 
      
 37 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 38 
     | 
    
         
            +
                      Codebeacon::Tracer.logger.error("Error in middleware: #{e.message}")
         
     | 
| 
      
 39 
     | 
    
         
            +
                      Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
         
     | 
| 
      
 40 
     | 
    
         
            +
                      # Ensure the request is processed even if tracing fails
         
     | 
| 
      
 41 
     | 
    
         
            +
                      response = @app.call(env) if response.nil?
         
     | 
| 
      
 42 
     | 
    
         
            +
                    end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    response
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,113 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require_relative 'models/node_builder'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative 'models/thread_local_call_tree_manager'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 6 
     | 
    
         
            +
                class Tracer
         
     | 
| 
      
 7 
     | 
    
         
            +
                  attr_reader :id, :tree_manager
         
     | 
| 
      
 8 
     | 
    
         
            +
                  attr_accessor :name, :description
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  def initialize(name = nil, description = nil)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @progress_logger = Codebeacon::Tracer.logger.newProgressLogger("calls traced")
         
     | 
| 
      
 12 
     | 
    
         
            +
                    @traces = [trace_call, trace_b_call, trace_return, trace_b_return]
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @name = name
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @description = description
         
     | 
| 
      
 15 
     | 
    
         
            +
                    @trace_id = SecureRandom.uuid
         
     | 
| 
      
 16 
     | 
    
         
            +
                    @tree_manager = ThreadLocalCallTreeManager.new(@trace_id)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  def id()
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @trace_id
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  def call_tree()
         
     | 
| 
      
 24 
     | 
    
         
            +
                    @tree_manager.current()
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  def start()
         
     | 
| 
      
 28 
     | 
    
         
            +
                    @progress_logger = Codebeacon::Tracer.logger.newProgressLogger("calls traced")
         
     | 
| 
      
 29 
     | 
    
         
            +
                    start_traces
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                  def stop()
         
     | 
| 
      
 33 
     | 
    
         
            +
                    stop_traces
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @progress_logger.finish()
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  def cleanup()
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @tree_manager.cleanup
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  def start_traces
         
     | 
| 
      
 42 
     | 
    
         
            +
                    dry_run_log = Codebeacon::Tracer.config.dry_run? ? "--DRY RUN-- " : ""
         
     | 
| 
      
 43 
     | 
    
         
            +
                    Codebeacon::Tracer.logger.info("#{dry_run_log}Starting trace: #{id}")
         
     | 
| 
      
 44 
     | 
    
         
            +
                    @traces.each do |trace|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      trace.enable
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def stop_traces
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @traces.each do |trace|
         
     | 
| 
      
 51 
     | 
    
         
            +
                      trace.disable
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
                    Codebeacon::Tracer.logger.info("END tracing")
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                  def enable_traces
         
     | 
| 
      
 57 
     | 
    
         
            +
                    start
         
     | 
| 
      
 58 
     | 
    
         
            +
                    return yield
         
     | 
| 
      
 59 
     | 
    
         
            +
                  ensure
         
     | 
| 
      
 60 
     | 
    
         
            +
                    stop
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                  def trace_call
         
     | 
| 
      
 64 
     | 
    
         
            +
                    trace(:call) do |tp|
         
     | 
| 
      
 65 
     | 
    
         
            +
                      NodeBuilder.trace_method_call(call_tree, tp, Kernel.caller[2..])
         
     | 
| 
      
 66 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 67 
     | 
    
         
            +
                      @progress_logger.increment()
         
     | 
| 
      
 68 
     | 
    
         
            +
                    end
         
     | 
| 
      
 69 
     | 
    
         
            +
                  end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                  def trace_b_call
         
     | 
| 
      
 72 
     | 
    
         
            +
                    trace(:b_call) do |tp|
         
     | 
| 
      
 73 
     | 
    
         
            +
                      NodeBuilder.trace_block_call(call_tree, tp, Kernel.caller[2..])
         
     | 
| 
      
 74 
     | 
    
         
            +
                    ensure
         
     | 
| 
      
 75 
     | 
    
         
            +
                      @progress_logger.increment()
         
     | 
| 
      
 76 
     | 
    
         
            +
                    end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  def trace_return
         
     | 
| 
      
 80 
     | 
    
         
            +
                    trace(:return) do |tp|
         
     | 
| 
      
 81 
     | 
    
         
            +
                      NodeBuilder.trace_return(call_tree, tp)
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                  def trace_b_return
         
     | 
| 
      
 86 
     | 
    
         
            +
                    trace(:b_return) do |tp|
         
     | 
| 
      
 87 
     | 
    
         
            +
                      NodeBuilder.trace_return(call_tree, tp)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    end
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  def trace(type)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    TracePoint.new(type) do |tp|
         
     | 
| 
      
 93 
     | 
    
         
            +
                      paths = [tp.path]
         
     | 
| 
      
 94 
     | 
    
         
            +
                      # capture calls and returns to skipped paths from non skipped paths. All I need is the return value to display in recorded files, but the code doesn't yet support this without tracing the entire call and return.
         
     | 
| 
      
 95 
     | 
    
         
            +
                      if [:call, :b_call, :return, :b_return].include?(type)
         
     | 
| 
      
 96 
     | 
    
         
            +
                        paths << Kernel.caller(1..1)[0]
         
     | 
| 
      
 97 
     | 
    
         
            +
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                      next if skip_methods?(paths)
         
     | 
| 
      
 99 
     | 
    
         
            +
                      yield tp
         
     | 
| 
      
 100 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 101 
     | 
    
         
            +
                      Codebeacon::Tracer.logger.error("TracePoint(#{type}) #{tp.path} #{e.message}")
         
     | 
| 
      
 102 
     | 
    
         
            +
                    end
         
     | 
| 
      
 103 
     | 
    
         
            +
                  end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                  def skip_methods?(paths)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    paths.all? do |path|
         
     | 
| 
      
 107 
     | 
    
         
            +
                      path.nil? || Codebeacon::Tracer.config.exclude_paths.any?{ |exclude_path| path.start_with?(exclude_path) } ||
         
     | 
| 
      
 108 
     | 
    
         
            +
                        Codebeacon::Tracer.config.local_methods_only? && !path.start_with?(Codebeacon::Tracer.config.root_path)
         
     | 
| 
      
 109 
     | 
    
         
            +
                    end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  end
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
              end
         
     | 
| 
      
 113 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,132 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require 'parser/current'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require_relative "codebeacon/tracer/version"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'securerandom'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            # Load all the source files
         
     | 
| 
      
 8 
     | 
    
         
            +
            Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', '*.rb')].each { |file| require file }
         
     | 
| 
      
 9 
     | 
    
         
            +
            Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'models', '*.rb')].each { |file| require file }
         
     | 
| 
      
 10 
     | 
    
         
            +
            Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'data', '*.rb')].each { |file| require file }
         
     | 
| 
      
 11 
     | 
    
         
            +
            Dir[File.join(File.dirname(__FILE__), 'codebeacon', 'tracer', 'src', 'rails', '*.rb')].each { |file| require file } if defined?(Rails::Railtie)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            module Codebeacon
         
     | 
| 
      
 14 
     | 
    
         
            +
              # The Tracer module provides tools to trace and analyze the runtime performance
         
     | 
| 
      
 15 
     | 
    
         
            +
              # of your Ruby applications. It captures method calls, execution times, and generates
         
     | 
| 
      
 16 
     | 
    
         
            +
              # reports to help identify bottlenecks.
         
     | 
| 
      
 17 
     | 
    
         
            +
              #
         
     | 
| 
      
 18 
     | 
    
         
            +
              # @example Tracing a block of code
         
     | 
| 
      
 19 
     | 
    
         
            +
              #   Codebeacon::Tracer.trace("My Trace", "Description of what I'm tracing") do |tracer|
         
     | 
| 
      
 20 
     | 
    
         
            +
              #     # Your code to analyze goes here
         
     | 
| 
      
 21 
     | 
    
         
            +
              #     some_method_to_analyze
         
     | 
| 
      
 22 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 23 
     | 
    
         
            +
              #
         
     | 
| 
      
 24 
     | 
    
         
            +
              module Tracer
         
     | 
| 
      
 25 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 26 
     | 
    
         
            +
                  # @return [ThreadLocalCallTreeManager] The current tree manager
         
     | 
| 
      
 27 
     | 
    
         
            +
                  attr_reader :tree_manager
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  # Returns the configuration object for Codebeacon::Tracer
         
     | 
| 
      
 30 
     | 
    
         
            +
                  # @return [Configuration] The configuration object
         
     | 
| 
      
 31 
     | 
    
         
            +
                  def config 
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @config ||= Configuration.new
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # Returns the current call tree
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # @return [CallTree] The current call tree
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def current_tree
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @tracer&.tree_manager&.current()
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  # Returns the logger instance
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # @return [Logger] The logger instance
         
     | 
| 
      
 43 
     | 
    
         
            +
                  def logger
         
     | 
| 
      
 44 
     | 
    
         
            +
                    config.logger
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  # Traces a block of code and collects runtime information
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #
         
     | 
| 
      
 49 
     | 
    
         
            +
                  # @param name [String, nil] Optional name for the trace
         
     | 
| 
      
 50 
     | 
    
         
            +
                  # @param description [String, nil] Optional description for the trace
         
     | 
| 
      
 51 
     | 
    
         
            +
                  # @yield [tracer] Yields the tracer object to the block
         
     | 
| 
      
 52 
     | 
    
         
            +
                  # @yieldparam tracer [Tracer] The tracer object
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # @return [Object] The result of the block
         
     | 
| 
      
 54 
     | 
    
         
            +
                  def trace(name = nil, description = nil)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 56 
     | 
    
         
            +
                      setup
         
     | 
| 
      
 57 
     | 
    
         
            +
                      @tracer = Tracer.new(name, description)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      result = @tracer.enable_traces do
         
     | 
| 
      
 59 
     | 
    
         
            +
                        yield @tracer
         
     | 
| 
      
 60 
     | 
    
         
            +
                      end
         
     | 
| 
      
 61 
     | 
    
         
            +
                      persist(@tracer.name, @tracer.description)
         
     | 
| 
      
 62 
     | 
    
         
            +
                      cleanup
         
     | 
| 
      
 63 
     | 
    
         
            +
                      result
         
     | 
| 
      
 64 
     | 
    
         
            +
                    rescue => e
         
     | 
| 
      
 65 
     | 
    
         
            +
                      Codebeacon::Tracer.logger.error("Error during tracing: #{e.message}")
         
     | 
| 
      
 66 
     | 
    
         
            +
                      Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
         
     | 
| 
      
 67 
     | 
    
         
            +
                      # Continue execution without crashing the application
         
     | 
| 
      
 68 
     | 
    
         
            +
                      yield nil if block_given?
         
     | 
| 
      
 69 
     | 
    
         
            +
                    end
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                  # Starts tracing without a block
         
     | 
| 
      
 73 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
      
 74 
     | 
    
         
            +
                  def start
         
     | 
| 
      
 75 
     | 
    
         
            +
                    setup
         
     | 
| 
      
 76 
     | 
    
         
            +
                    @tracer = Tracer.new()
         
     | 
| 
      
 77 
     | 
    
         
            +
                    @tracer.start
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  # Stops tracing and persists the results
         
     | 
| 
      
 81 
     | 
    
         
            +
                  # @return [void]
         
     | 
| 
      
 82 
     | 
    
         
            +
                  def stop
         
     | 
| 
      
 83 
     | 
    
         
            +
                    @tracer.stop
         
     | 
| 
      
 84 
     | 
    
         
            +
                    persist
         
     | 
| 
      
 85 
     | 
    
         
            +
                    cleanup
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  private def setup
         
     | 
| 
      
 89 
     | 
    
         
            +
                    Codebeacon::Tracer.config.setup
         
     | 
| 
      
 90 
     | 
    
         
            +
                    @app_node = NodeSource.new('app', Codebeacon::Tracer.config.root_path)
         
     | 
| 
      
 91 
     | 
    
         
            +
                    @gem_node = NodeSource.new('gem', Codebeacon::Tracer.config.gem_path)
         
     | 
| 
      
 92 
     | 
    
         
            +
                    @rubylib_node = NodeSource.new('rubylib', Codebeacon::Tracer.config.rubylib_path)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  private def persist(name = "", description = "")
         
     | 
| 
      
 96 
     | 
    
         
            +
                    unless Codebeacon::Tracer.config.dry_run?
         
     | 
| 
      
 97 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 98 
     | 
    
         
            +
                        schema = DatabaseSchema.new
         
     | 
| 
      
 99 
     | 
    
         
            +
                        schema.create_tables
         
     | 
| 
      
 100 
     | 
    
         
            +
                        DatabaseSchema.trim_db_files
         
     | 
| 
      
 101 
     | 
    
         
            +
                        pm = PersistenceManager.new(schema.db)
         
     | 
| 
      
 102 
     | 
    
         
            +
                        ordered_sources = [ @app_node, @gem_node, @rubylib_node ]
         
     | 
| 
      
 103 
     | 
    
         
            +
                        pm.save_metadata(name, description)
         
     | 
| 
      
 104 
     | 
    
         
            +
                        pm.save_node_sources(ordered_sources)
         
     | 
| 
      
 105 
     | 
    
         
            +
                        pm.save_trees(@tracer.tree_manager.trees)
         
     | 
| 
      
 106 
     | 
    
         
            +
                        schema.create_indexes
         
     | 
| 
      
 107 
     | 
    
         
            +
                        schema.db.close
         
     | 
| 
      
 108 
     | 
    
         
            +
                        touch_refresh
         
     | 
| 
      
 109 
     | 
    
         
            +
                      rescue => e
         
     | 
| 
      
 110 
     | 
    
         
            +
                        Codebeacon::Tracer.logger.error("Error during persistence: #{e.message}")
         
     | 
| 
      
 111 
     | 
    
         
            +
                        Codebeacon::Tracer.logger.error(e.backtrace.join("\n")) if Codebeacon::Tracer.config.debug?
         
     | 
| 
      
 112 
     | 
    
         
            +
                        # Continue execution without crashing the application
         
     | 
| 
      
 113 
     | 
    
         
            +
                      end
         
     | 
| 
      
 114 
     | 
    
         
            +
                    end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                  private def cleanup
         
     | 
| 
      
 118 
     | 
    
         
            +
                    NodeSource.clear
         
     | 
| 
      
 119 
     | 
    
         
            +
                    @tracer.cleanup
         
     | 
| 
      
 120 
     | 
    
         
            +
                  end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                  private def touch_refresh
         
     | 
| 
      
 123 
     | 
    
         
            +
                    FileUtils.mkdir_p(Codebeacon::Tracer.config.tmp_dir) unless File.exist?(Codebeacon::Tracer.config.tmp_dir)
         
     | 
| 
      
 124 
     | 
    
         
            +
                    if File.exist?(Codebeacon::Tracer.config.refresh_path)
         
     | 
| 
      
 125 
     | 
    
         
            +
                      File.utime(Time.now, Time.now, Codebeacon::Tracer.config.refresh_path)
         
     | 
| 
      
 126 
     | 
    
         
            +
                    else
         
     | 
| 
      
 127 
     | 
    
         
            +
                      File.open(Codebeacon::Tracer.config.refresh_path, 'w') {}
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  end
         
     | 
| 
      
 130 
     | 
    
         
            +
                end
         
     | 
| 
      
 131 
     | 
    
         
            +
              end
         
     | 
| 
      
 132 
     | 
    
         
            +
            end
         
     |