class ChaosDetector::Tracker
Constants
- REGEX_MODULE_UNDECORATE
- TRACE_METHOD_EVENTS
Attributes
options[R]
walkman[R]
Public Class Methods
new(options:)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 22 def initialize(options:) raise ArgumentError, '#initialize requires options' if options.nil? @options = options @total_frames = 0 @total_traces = 0 apply_options end
Public Instance Methods
record()
click to toggle source
# File lib/chaos_detector/tracker.rb, line 31 def record log("Detecting chaos at #{@app_root_path}") # log(caller_locations.join("\n\t->\t")) # log("") @stopped = false @walkman.record_start @total_traces = 0 @trace = TracePoint.new(*TRACE_METHOD_EVENTS) do |tracepoint| if @stopped @trace.disable log('Tracing stopped; stopping immediately.') next end tp_path = tracepoint.path next if full_path_skip?(tp_path) # puts("Tracepoint: #{tracepoint.inspect}") # log_mod_details(tracepoint) tp_class = tracepoint.defined_class tp_event = tracepoint.event fn_name = tracepoint.callee_id.to_s fn_line = tracepoint.lineno fn_path = localize_path(tp_path) binding_info = tracepoint.self.send(:class)&.name caller_locations = tracepoint.self.send(:caller_locations) # trace_mod_details(tracepoint) tracepoint.disable do mod_info = mod_info_at(tp_class, mod_full_path: tp_path) # puts "mod_info: #{mod_info} #{tp_class.respond_to?(:superclass) && tp_class.superclass}" next unless mod_info @total_traces += 1 fn_info = fn_info_at(fn_name, fn_line: fn_line, fn_path: fn_path) caller_info = extract_caller(caller_locations: caller_locations, fn_info: fn_info) write_event_frame(tp_event, fn_info: fn_info, mod_info: mod_info, caller_info: caller_info) # if tp_event==:call # puts('<' * 50) # puts("Binding: #{binding_info}") # puts("Module: #{mod_info}") # puts("Function: #{fn_info}") # puts("Caller: #{caller_info.inspect}") if caller_info # puts('>' * 50) # puts # end # Detect superclass association: ChaosUtils.with(superclass_mod_info(tp_class)) do |super_mod_info| # puts "Would superclass #{mod_info} with #{super_mod_info}" write_event_frame(:superclass, fn_info: fn_info, mod_info: mod_info, caller_info: super_mod_info) end # Detect associations: # puts "UGGGGG: #{tp_class.singleton_class.included_modules}" ancestor_mod_infos(tp_class, tp_class.included_modules).each do |agg_mod_info| # puts "Would ancestors with #{agg_mod_info}" write_event_frame(:association, fn_info: fn_info, mod_info: mod_info, caller_info: agg_mod_info) end # DerivedFracker.singleton_class.included_modules # MixinCD, Kernel ancestor_mod_infos(tp_class, tp_class.singleton_class.included_modules).each do |agg_mod_info| # puts "WOULD CLASS ancestors with #{agg_mod_info}" write_event_frame(:class_association, fn_info: fn_info, mod_info: mod_info, caller_info: agg_mod_info) end # Detect class associations: # ancestor_mod_infos(tp_class).each do |agg_mod_info| # puts "Would ancestors with #{agg_mod_info}" # write_event_frame(:association, fn_info: fn_info, mod_info: mod_info, caller_info: super_mod_info) # end end end @trace.enable true end
stop()
click to toggle source
# File lib/chaos_detector/tracker.rb, line 123 def stop @stopped = true @trace&.disable log("Stopping after total traces: #{@total_traces}") @walkman.stop end
undecorate_module_name(mod_name)
click to toggle source
Blank class get mod_class for tracepoint. [#<Class:#<Parslet::Context:0x00007fa90ee06c80>>] MMMM >>> (word), (default), (word), (lib/email_parser.rb):L106, (#<Parslet::Context:0x00007fa90ee06c80>)
# File lib/chaos_detector/tracker.rb, line 137 def undecorate_module_name(mod_name) return nil if ChaosUtils.naught?(mod_name) return mod_name unless mod_name.start_with?('#') plain_name = nil caps = mod_name.match(REGEX_MODULE_UNDECORATE)&.captures # puts "CAP #{mod_name}: #{caps}" if caps && caps.length > 0 caps.delete('Class:') caps.compact! plain_name = caps.first plain_name&.chomp!(':') end plain_name || mod_name end
write_event_frame(event, fn_info:, mod_info:, caller_info:)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 111 def write_event_frame(event, fn_info:, mod_info:, caller_info:) ChaosDetector::Stacker::Frame.new( event: event, mod_info: mod_info, fn_info: fn_info, caller_info: caller_info ).tap do |frame| @walkman.write_frame(frame) @total_frames += 1 end end
Private Instance Methods
ancestor_mod_infos(clz, clz_modules)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 192 def ancestor_mod_infos(clz, clz_modules) sup_clz = clz.superclass rescue nil ancestors = clz_modules.filter_map do |c| if c != clz && (sup_clz.nil? || c != sup_clz) mod_info_at(c) end end ancestors.compact end
apply_options()
click to toggle source
# File lib/chaos_detector/tracker.rb, line 156 def apply_options @walkman = ChaosDetector::Walkman.new(options: @options) @app_root_path = ChaosUtils.with(@options.app_root_path) { |p| Pathname.new(p)&.to_s} end
check_name(mod_nm)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 280 def check_name(mod_nm) ChaosUtils.aught?(mod_nm) && !mod_nm.strip.start_with?('#') end
extract_caller(caller_locations:, fn_info:)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 161 def extract_caller(caller_locations:, fn_info:) callers = caller_locations.select do |bt| !full_path_skip?(bt.absolute_path) && ChaosUtils.aught?(bt.base_label) && !bt.base_label.start_with?('<') end frame_at = callers.index { |bt| bt.base_label == fn_info.fn_name && localize_path(bt.absolute_path) == fn_info.fn_path } bt_caller = frame_at.nil? ? nil : callers[frame_at + 1] ChaosUtils.with(bt_caller) do |bt| ChaosDetector::Stacker::FnInfo.new( fn_name: bt.base_label, fn_line: bt.lineno, fn_path: localize_path(bt.absolute_path) ) end end
fn_info_at(fn_name, fn_line:, fn_path:)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 216 def fn_info_at(fn_name, fn_line:, fn_path:) ChaosDetector::Stacker::FnInfo.new( fn_name: fn_name, fn_line: fn_line, fn_path: fn_path, ) end
full_path_skip?(path)
click to toggle source
TODO: MAKE more LIKE module_skip below:
# File lib/chaos_detector/tracker.rb, line 225 def full_path_skip?(path) return true unless ChaosUtils.aught?(path) if !(@app_root_path && path.start_with?(@app_root_path)) true else rel_path = localize_path(path) @options.ignore_paths.any? { |p| rel_path.start_with?(p)} # false end end
localize_path(path)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 262 def localize_path(path) # @app_root_path.relative_path_from(Pathname.new(path).cleanpath).to_s return '' unless ChaosUtils.aught?(path) p = Pathname.new(path).cleanpath.to_s p.sub!(@app_root_path, '') if @app_root_path local_path = p.start_with?('/') ? p[1..-1] : p local_path.to_s end
log(msg, **opts)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 272 def log(msg, **opts) ChaosUtils.log_msg(msg, subject: 'Tracker', **opts) end
mod_info_at(mod_class, mod_full_path: nil)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 204 def mod_info_at(mod_class, mod_full_path: nil) return nil unless mod_class mod_name = mod_name_from_class(mod_class) if ChaosUtils.aught?(mod_name) mod_type = mod_type_from_class(mod_class) mod_fp = ChaosUtils.aught?(mod_full_path) ? mod_full_path : nil mod_fp ||= mod_class.const_source_location(mod_name)&.first safe_mod_info(mod_name, mod_type, mod_fp) end end
mod_name_from_class(clz)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 255 def mod_name_from_class(clz) mod_name = clz.name mod_name = clz.to_s unless check_name(mod_name) undecorate_module_name(mod_name) end
mod_type_from_class(clz)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 243 def mod_type_from_class(clz) case clz when Class :class when Module :module else log "Unknown mod_type: #{clz}" :nil end end
module_skip?(mod_name)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 237 def module_skip?(mod_name) ChaosUtils.with(mod_name) do |mod| @options.ignore_modules.any? { |m| mod.start_with?(m)} end end
safe_mod_info(mod_name, mod_type, mod_full_path)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 284 def safe_mod_info(mod_name, mod_type, mod_full_path) return nil if full_path_skip?(mod_full_path) return nil if module_skip?(mod_name) # puts ['mod_full_path', mod_full_path].inspect ChaosDetector::Stacker::ModInfo.new( mod_name: mod_name, mod_path: localize_path(mod_full_path), mod_type: mod_type ) end
superclass_mod_info(clz)
click to toggle source
# File lib/chaos_detector/tracker.rb, line 179 def superclass_mod_info(clz) return nil unless clz&.respond_to?(:superclass) sup_clz = clz.superclass # puts "BOOOO::: #{clz.superclass} <> #{sup_clz} ~> ChaosUtils.aught?(sup_clz)" return nil unless ChaosUtils.aught?(sup_clz) # puts "DDDDDDDDDDD::: #{sup_clz&.name}" mod_info_at(sup_clz) end
trace_mod_details(tp, label: 'ModDetails')
click to toggle source
# File lib/chaos_detector/tracker.rb, line 276 def trace_mod_details(tp, label: 'ModDetails') log format('Tracepoint [%s] (%s): %s / %s [%s / %s]', label, tp.event, tp.defined_class, tp.self.class, tp.defined_class&.name, tp.self.class&.name) end