chaos_detector 0.4.9
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/bin/detect_chaos +31 -0
- data/lib/chaos_detector.rb +22 -0
- data/lib/chaos_detector/chaos_graphs/chaos_edge.rb +32 -0
- data/lib/chaos_detector/chaos_graphs/chaos_graph.rb +389 -0
- data/lib/chaos_detector/chaos_graphs/domain_metrics.rb +19 -0
- data/lib/chaos_detector/chaos_graphs/domain_node.rb +57 -0
- data/lib/chaos_detector/chaos_graphs/function_node.rb +112 -0
- data/lib/chaos_detector/chaos_graphs/module_node.rb +86 -0
- data/lib/chaos_detector/chaos_utils.rb +57 -0
- data/lib/chaos_detector/graph_theory/appraiser.rb +162 -0
- data/lib/chaos_detector/graph_theory/edge.rb +76 -0
- data/lib/chaos_detector/graph_theory/graph.rb +144 -0
- data/lib/chaos_detector/graph_theory/loop_detector.rb +32 -0
- data/lib/chaos_detector/graph_theory/node.rb +70 -0
- data/lib/chaos_detector/graph_theory/node_metrics.rb +68 -0
- data/lib/chaos_detector/graph_theory/reduction.rb +40 -0
- data/lib/chaos_detector/graphing/directed_graphs.rb +396 -0
- data/lib/chaos_detector/graphing/graphs.rb +129 -0
- data/lib/chaos_detector/graphing/matrix_graphs.rb +101 -0
- data/lib/chaos_detector/navigator.rb +237 -0
- data/lib/chaos_detector/options.rb +51 -0
- data/lib/chaos_detector/stacker/comp_info.rb +42 -0
- data/lib/chaos_detector/stacker/fn_info.rb +44 -0
- data/lib/chaos_detector/stacker/frame.rb +34 -0
- data/lib/chaos_detector/stacker/frame_stack.rb +63 -0
- data/lib/chaos_detector/stacker/mod_info.rb +24 -0
- data/lib/chaos_detector/tracker.rb +276 -0
- data/lib/chaos_detector/utils/core_util.rb +117 -0
- data/lib/chaos_detector/utils/fs_util.rb +49 -0
- data/lib/chaos_detector/utils/lerp_util.rb +20 -0
- data/lib/chaos_detector/utils/log_util.rb +45 -0
- data/lib/chaos_detector/utils/str_util.rb +90 -0
- data/lib/chaos_detector/utils/tensor_util.rb +21 -0
- data/lib/chaos_detector/walkman.rb +214 -0
- metadata +147 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
# naught?("")
|
2
|
+
# naught?(0)
|
3
|
+
# naught?([])
|
4
|
+
# naught?("foobar")
|
5
|
+
# naught?(0)
|
6
|
+
# naught?([])
|
7
|
+
# module ChaosDetector
|
8
|
+
|
9
|
+
# = ChaosDetector::Utils::CoreUtil::with
|
10
|
+
module ChaosDetector
|
11
|
+
module Utils
|
12
|
+
module CoreUtil
|
13
|
+
class AssertError < StandardError; end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def enum(*values)
|
17
|
+
Module.new do |mod|
|
18
|
+
values.each_with_index do |v, _i|
|
19
|
+
mod.const_set(v.to_s.upcase, v.to_s.downcase.to_sym)
|
20
|
+
# mod.const_set(v.to_s.upcase, 2**i)
|
21
|
+
end
|
22
|
+
|
23
|
+
def mod.values
|
24
|
+
constants
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def naught?(obj)
|
30
|
+
if obj.nil?
|
31
|
+
true
|
32
|
+
elsif obj.is_a?(FalseClass)
|
33
|
+
true
|
34
|
+
elsif obj.is_a?(TrueClass)
|
35
|
+
false
|
36
|
+
elsif obj.is_a?(String)
|
37
|
+
obj.strip.empty?
|
38
|
+
elsif obj.is_a?(Enumerable)
|
39
|
+
obj.none? { |o| aught?(o) }
|
40
|
+
elsif obj.is_a?(Numeric)
|
41
|
+
obj == 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def aught?(obj)
|
46
|
+
!naught?(obj)
|
47
|
+
end
|
48
|
+
|
49
|
+
def with(obj, aught:false)
|
50
|
+
raise ArgumentError('with requires block') unless block_given?
|
51
|
+
|
52
|
+
yield obj if (aught ? aught?(obj) : !!obj)
|
53
|
+
end
|
54
|
+
|
55
|
+
def assert(expected_result=true, msg=nil, &block)
|
56
|
+
if block.nil? && !expected_result
|
57
|
+
raise AssertError, "Assertion failed. #{msg}"
|
58
|
+
elsif !block.nil? && block.call != expected_result
|
59
|
+
raise AssertError, "Assertion failed. #{msg}\n\t#{block.source_location}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return subset of given properties not contained within given object
|
64
|
+
def properties_complement(props, obj:)
|
65
|
+
return props if obj.nil?
|
66
|
+
raise ArgumentError, 'props is required.' unless props
|
67
|
+
|
68
|
+
puts "(#{obj.class} )props: #{props}"
|
69
|
+
|
70
|
+
props - case obj
|
71
|
+
when Hash
|
72
|
+
puts 'KKKKK'
|
73
|
+
puts "obj.keys: #{obj.keys}"
|
74
|
+
obj.keys
|
75
|
+
|
76
|
+
else
|
77
|
+
puts "PPPPP #{obj.class}"
|
78
|
+
puts "obj.public_methods: #{obj.public_methods}"
|
79
|
+
obj.public_methods
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Built-in self-testing:
|
85
|
+
# ChaosDetector::Utils::CoreUtil.test
|
86
|
+
def test
|
87
|
+
puts('Testing ChaosDetector::Utils::CoreUtil')
|
88
|
+
assert(true, 'Naught detects blank string') {naught?('')}
|
89
|
+
assert(true, 'Naught detects blank string with space') {naught?(' ')}
|
90
|
+
assert(true, 'Naught detects int 0') {naught?(0)}
|
91
|
+
assert(true, 'Naught detects float 0.0') {naught?(0.0)}
|
92
|
+
assert(true, 'Naught detects empty array') {naught?([])}
|
93
|
+
assert(true, 'Naught detects empty hash') {naught?({})}
|
94
|
+
assert(false, 'Naught real string') {naught?('foobar')}
|
95
|
+
assert(false, 'Naught non-zero int') {naught?(1)}
|
96
|
+
assert(false, 'Naught non-zero float') {naught?(33.33)}
|
97
|
+
assert(false, 'Naught non-empty array') {naught?(['stuff'])}
|
98
|
+
assert(false, 'Naught non-empty hash') {naught?({ foo: 'bar' })}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module ChaosAttr
|
103
|
+
def chaos_attr(attribute_name, default_val=nil, &block)
|
104
|
+
# raise 'Default value or block required' unless !default_val.nil? || block
|
105
|
+
sym = attribute_name&.to_sym
|
106
|
+
raise ArgumentError, 'attribute_name is required and convertible to symbol.' if sym.nil?
|
107
|
+
|
108
|
+
define_method(sym) do
|
109
|
+
instance_variable_get("@#{sym}") || (block.nil? ? default_val : block.call)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method("#{sym}=") { |val| instance_variable_set("@#{sym}", val) }
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
require_relative 'core_util'
|
4
|
+
|
5
|
+
module ChaosDetector
|
6
|
+
module Utils
|
7
|
+
module FSUtil
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Relative path:
|
11
|
+
def rel_path(dir_path, from_path:)
|
12
|
+
pathname = Pathname.new(dir_path)
|
13
|
+
base_path = Pathname.new(from_path).cleanpath
|
14
|
+
pathname.relative_path_from(base_path).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
# Ensure directory and all its parents exist, like (mkdir -p):
|
18
|
+
def ensure_dirpath(dirpath)
|
19
|
+
raise ArgumentError, '#ensure_paths_to_file requires dirpath' if nay? dirpath
|
20
|
+
|
21
|
+
FileUtils.mkdir_p(dirpath)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Ensure file's directory and all its parents exist, then write given string to it:
|
25
|
+
def safe_file_write(filepath, content: nil)
|
26
|
+
raise ArgumentError, '#write_to_file requires filepath' if nay? filepath
|
27
|
+
|
28
|
+
ensure_paths_to_file(filepath)
|
29
|
+
File.write(filepath, content)
|
30
|
+
filepath
|
31
|
+
end
|
32
|
+
|
33
|
+
# Ensure file's directory and all its parents exist, like (mkdir -p):
|
34
|
+
def ensure_paths_to_file(filepath)
|
35
|
+
raise ArgumentError, '#ensure_paths_to_file requires filepath' if nay? filepath
|
36
|
+
|
37
|
+
dirpath = File.split(filepath).first
|
38
|
+
raise "dirpath couldn't be obtained from #{filepath}" if nay? dirpath
|
39
|
+
|
40
|
+
ensure_dirpath(dirpath)
|
41
|
+
end
|
42
|
+
|
43
|
+
def nay?(obj)
|
44
|
+
ChaosDetector::Utils::CoreUtil.naught?(obj)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ChaosDetector
|
2
|
+
module Utils
|
3
|
+
module LerpUtil
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def delerp(val, min:, max:)
|
7
|
+
return 0.0 if min==max
|
8
|
+
(val - min).to_f / (max - min)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Linear interpolation between min and max:
|
12
|
+
# @arg pct is percentage where 1.0 represents 100%
|
13
|
+
def lerp(pct, min:, max:)
|
14
|
+
return 0.0 if min==max
|
15
|
+
(max-min) * pct.to_f + min
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'core_util'
|
2
|
+
require_relative 'str_util'
|
3
|
+
|
4
|
+
module ChaosDetector
|
5
|
+
module Utils
|
6
|
+
module LogUtil
|
7
|
+
class << self
|
8
|
+
# Simple logging, more later
|
9
|
+
def log(msg, object: nil, subject: nil)
|
10
|
+
# raise ArgumentError, "no message to log" if nay?(msg)
|
11
|
+
return if nay?(msg)
|
12
|
+
|
13
|
+
subj = d(subject, clamp: :brace)
|
14
|
+
obj = d(object, clamp: :bracket, prefix: ': ')
|
15
|
+
message = d(msg, prefix: subj, suffix: obj)
|
16
|
+
|
17
|
+
if block_given?
|
18
|
+
print_line(d(message, prefix: 'Starting: '))
|
19
|
+
result = yield
|
20
|
+
print_line(d(message, prefix: 'Complete: ', suffix: d(result)))
|
21
|
+
else
|
22
|
+
print_line(message)
|
23
|
+
end
|
24
|
+
message
|
25
|
+
end
|
26
|
+
|
27
|
+
def print_line(msg, *opts)
|
28
|
+
# print("#{msg}\n", opts)
|
29
|
+
# nil
|
30
|
+
Kernel.puts(msg, opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias pp print_line
|
34
|
+
|
35
|
+
def nay?(obj)
|
36
|
+
ChaosDetector::Utils::CoreUtil.naught?(obj)
|
37
|
+
end
|
38
|
+
|
39
|
+
def d(text, *args)
|
40
|
+
ChaosDetector::Utils::StrUtil.decorate(text, *args)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative 'core_util'
|
2
|
+
module ChaosDetector
|
3
|
+
module Utils
|
4
|
+
module StrUtil
|
5
|
+
STR_INDENT = ' '.freeze
|
6
|
+
STR_BLANK = ''.freeze
|
7
|
+
STR_NS_SEP = '::'.freeze
|
8
|
+
SPACE = ' '.freeze
|
9
|
+
SCORE = '_'.freeze
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def decorate_pair(source, dest, indent_length: 0, clamp: :angle, join_str: ' ')
|
14
|
+
decorate("#{decorate(source)}#{decorate(dest, prefix: join_str)}", clamp: clamp, indent_length: indent_length)
|
15
|
+
end
|
16
|
+
|
17
|
+
def decorate_tuple(tuple, indent_length: 0, clamp: :angle, join_str: ' ')
|
18
|
+
body = tuple.map { |t| decorate(t, indent_length: indent_length)}.join(join_str)
|
19
|
+
decorate(body, clamp: clamp, indent_length: indent_length)
|
20
|
+
end
|
21
|
+
|
22
|
+
def decorate(text, clamp: :nil, prefix: nil, suffix: nil, sep: nil, indent_length: 0)
|
23
|
+
return '' if nay? text
|
24
|
+
|
25
|
+
clamp_pre, clamp_post = clamp_chars(clamp: clamp)
|
26
|
+
indent("#{prefix}#{sep}#{clamp_pre}#{text}#{clamp_post}#{sep}#{suffix}", indent_length)
|
27
|
+
end
|
28
|
+
|
29
|
+
def humanize_module(mod_name, max_segments: 2, sep_token: STR_NS_SEP)
|
30
|
+
return '' if nay? mod_name
|
31
|
+
raise ArgumentError, 'Must have at least 1 segment.' if max_segments < 1
|
32
|
+
|
33
|
+
mod_name.split(sep_token).last(max_segments).join(sep_token)
|
34
|
+
end
|
35
|
+
|
36
|
+
def snakeize(obj)
|
37
|
+
obj.to_s.gsub(/[^a-zA-Z\d\s:]/, SCORE)
|
38
|
+
end
|
39
|
+
|
40
|
+
def blank?(obj)
|
41
|
+
obj.nil? || obj.to_s.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def squish(str)
|
45
|
+
str.to_s.strip.split.map(&:strip).join(SPACE)
|
46
|
+
end
|
47
|
+
|
48
|
+
def titleize(obj)
|
49
|
+
obj.to_s.split(SCORE).map(&:capitalize).join(SPACE)
|
50
|
+
end
|
51
|
+
|
52
|
+
alias d decorate
|
53
|
+
|
54
|
+
def clamp_chars(clamp: :none)
|
55
|
+
case clamp
|
56
|
+
when :angle, :arrow
|
57
|
+
['<', '>']
|
58
|
+
when :brace
|
59
|
+
['{', '}']
|
60
|
+
when :bracket
|
61
|
+
['[', ']']
|
62
|
+
when :italic, :emphasize
|
63
|
+
%w[_ _]
|
64
|
+
when :strong, :bold, :stars
|
65
|
+
['**', '**']
|
66
|
+
when :quotes, :double_quotes
|
67
|
+
['"', '"']
|
68
|
+
when :ticks, :single_quotes
|
69
|
+
["'", "'"]
|
70
|
+
when :none
|
71
|
+
[STR_BLANK, STR_BLANK]
|
72
|
+
else # :parens, :parentheses
|
73
|
+
['(', ')']
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def indent(text, indent_length=1)
|
78
|
+
return '' if nay? text
|
79
|
+
return text unless indent_length
|
80
|
+
|
81
|
+
"#{STR_INDENT * indent_length}#{text}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def nay?(obj)
|
85
|
+
ChaosDetector::Utils::CoreUtil.naught?(obj)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'matrix'
|
2
|
+
require_relative 'lerp_util'
|
3
|
+
module ChaosDetector
|
4
|
+
module Utils
|
5
|
+
module TensorUtil
|
6
|
+
class << self
|
7
|
+
# Return new matrix that is normalized from 0.0(min) to 1.0(max)
|
8
|
+
def normalize_matrix(matrix)
|
9
|
+
mag = matrix.row_size
|
10
|
+
raise ArgumentError if matrix.column_size != mag
|
11
|
+
|
12
|
+
lo, hi = matrix.minmax
|
13
|
+
|
14
|
+
Matrix.build(mag) do |row, col|
|
15
|
+
ChaosDetector::Utils::LerpUtil.delerp(matrix[row, col], min: lo, max: hi)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
require 'chaos_detector/utils/fs_util'
|
5
|
+
require 'chaos_detector/chaos_utils'
|
6
|
+
|
7
|
+
require_relative 'options'
|
8
|
+
require_relative 'stacker/frame'
|
9
|
+
|
10
|
+
# TODO: add traversal types to find depth, coupling in various ways (directory/package/namespace):
|
11
|
+
module ChaosDetector
|
12
|
+
class Walkman
|
13
|
+
PLAYBACK_MSG = "Playback error on line number %d of pre-recorded CSV %s:\n %s\n %s".freeze
|
14
|
+
CSV_HEADER = %w[ROWNUM EVENT MOD_NAME MOD_TYPE FN_PATH FN_LINE FN_NAME CALLER_TYPE CALLER_PATH CALLER_INFO CALLER_NAME].freeze
|
15
|
+
COL_COUNT = CSV_HEADER.length
|
16
|
+
COL_INDEXES = CSV_HEADER.map.with_index { |col, i| [col.downcase.to_sym, i]}.to_h
|
17
|
+
|
18
|
+
DEFALT_BUFFER_LENGTH = 1000
|
19
|
+
|
20
|
+
def initialize(options:)
|
21
|
+
@buffer_length = options.walkman_buffer_length || DEFALT_BUFFER_LENGTH
|
22
|
+
@options = options
|
23
|
+
flush_csv
|
24
|
+
@csv_path = nil
|
25
|
+
@log_buffer = []
|
26
|
+
@rownum = 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_start
|
30
|
+
flush_csv
|
31
|
+
@csv_path = nil
|
32
|
+
@log_buffer = []
|
33
|
+
@rownum = 0
|
34
|
+
autosave_csv
|
35
|
+
init_file_with_header(csv_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return frame at given index or nil if nothing:
|
39
|
+
def frame_at(row_index:)
|
40
|
+
frames_within(row_range: row_index..row_index).first
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return any frames within specified row range; empty array if not found:
|
44
|
+
def frames_within(row_range: nil)
|
45
|
+
to_enum(:playback, row_range: row_range).map { |_r, frame| frame }
|
46
|
+
end
|
47
|
+
|
48
|
+
# Play back CSV from path configured in Walkman options
|
49
|
+
# @param row_range optionally specify range of rows. Leave nil for all.
|
50
|
+
# yields each row as
|
51
|
+
# frame A Frame object with its attributes contained in the CSV row
|
52
|
+
def playback(row_range: nil)
|
53
|
+
started_at = Time.now
|
54
|
+
|
55
|
+
if row_range
|
56
|
+
log('Walkman replaying CSV with (row_range) %d/%d lines: %s' % [
|
57
|
+
[count, row_range.max].min,
|
58
|
+
count,
|
59
|
+
csv_path
|
60
|
+
])
|
61
|
+
else
|
62
|
+
log("Walkman replaying CSV with #{count} lines: #{csv_path}")
|
63
|
+
end
|
64
|
+
|
65
|
+
@rownum = 0
|
66
|
+
row_cur = nil
|
67
|
+
CSV.foreach(csv_path, headers: true) do |row|
|
68
|
+
row_cur = row
|
69
|
+
yield @rownum, playback_row(row) if row_range.nil? || row_range.include?(@rownum)
|
70
|
+
@rownum += 1
|
71
|
+
break if row_range && @rownum > row_range.max
|
72
|
+
end
|
73
|
+
rescue StandardError => e
|
74
|
+
log("%s:\n%s" % [e.to_s, e.backtrace.join("\n")])
|
75
|
+
raise ScriptError, log(format(PLAYBACK_MSG, @rownum, csv_path, row_cur, e.inspect))
|
76
|
+
ensure
|
77
|
+
log('Replayed %d items in %.2f seconds' % [@rownum, Time.now - started_at])
|
78
|
+
end
|
79
|
+
|
80
|
+
def count
|
81
|
+
`wc -l #{csv_path}`.to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
# Call when done to flush any buffers as necessary
|
85
|
+
def stop
|
86
|
+
flush_csv
|
87
|
+
log("Stopped with #{count} lines.")
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def csv_path
|
92
|
+
@csv_path ||= @options.path_with_root(key: :frame_csv_path)
|
93
|
+
end
|
94
|
+
|
95
|
+
def autosave_csv
|
96
|
+
csvp = csv_path
|
97
|
+
if FileTest.exist?(csvp)
|
98
|
+
1.upto(100) do |n|
|
99
|
+
p = "#{csvp}.#{n}"
|
100
|
+
next if FileTest.exist?(p)
|
101
|
+
|
102
|
+
log("Autosaving #{csvp} to #{p}")
|
103
|
+
cp_result = `cp #{csvp} #{p}`
|
104
|
+
log(cp_result)
|
105
|
+
break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def buffered_trigger
|
111
|
+
if @log_buffer.length > @buffer_length
|
112
|
+
# log("Triggering flush @#{@log_buffer.length} / @buffer_length")
|
113
|
+
flush_csv
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def log(msg, **opts)
|
118
|
+
ChaosUtils.log_msg(msg, subject: 'Walkman', **opts)
|
119
|
+
end
|
120
|
+
|
121
|
+
def flush_csv
|
122
|
+
# log("Flushing...")
|
123
|
+
if @log_buffer&.any?
|
124
|
+
CSV.open(csv_path, 'a') do |csv|
|
125
|
+
@log_buffer.each do |log_line|
|
126
|
+
csv << log_line
|
127
|
+
end
|
128
|
+
end
|
129
|
+
@log_buffer.clear
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def write_frame(frame, frame_offset: nil)
|
134
|
+
csv_row = [@rownum]
|
135
|
+
csv_row.concat(frame_csv_fields(frame))
|
136
|
+
|
137
|
+
@log_buffer << csv_row
|
138
|
+
@rownum += 1
|
139
|
+
buffered_trigger
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def csv_row_val(row, col_header)
|
145
|
+
r = COL_INDEXES[col_header]
|
146
|
+
raise ArgumentError, "#{col_header} not found in CSV_HEADER: #{CSV_HEADER}" if r.nil? || r < 0 || r > COL_COUNT
|
147
|
+
|
148
|
+
row[r]
|
149
|
+
end
|
150
|
+
|
151
|
+
def frame_csv_fields(f)
|
152
|
+
[
|
153
|
+
f.event,
|
154
|
+
f.mod_info&.mod_name,
|
155
|
+
f.mod_info&.mod_type,
|
156
|
+
f.fn_info.fn_path,
|
157
|
+
f.fn_info.fn_line,
|
158
|
+
f.fn_info.fn_name,
|
159
|
+
f.caller_info&.component_type,
|
160
|
+
f.caller_info&.path,
|
161
|
+
f.caller_info&.info,
|
162
|
+
f.caller_info&.name
|
163
|
+
]
|
164
|
+
end
|
165
|
+
|
166
|
+
def init_file_with_header(filepath)
|
167
|
+
ChaosDetector::Utils::FSUtil.ensure_paths_to_file(filepath)
|
168
|
+
File.open(filepath, 'w') { |f| f.puts CSV_HEADER.join(',')}
|
169
|
+
end
|
170
|
+
|
171
|
+
# Play back a single given row
|
172
|
+
# returns the event and frame as described in #playback
|
173
|
+
def playback_row(row)
|
174
|
+
event = csv_row_val(row, :event)
|
175
|
+
fn_path = csv_row_val(row, :fn_path)
|
176
|
+
fn_line = csv_row_val(row, :fn_line)&.to_i
|
177
|
+
|
178
|
+
mod_info = ChaosDetector::Stacker::ModInfo.new(
|
179
|
+
mod_name: csv_row_val(row, :mod_name),
|
180
|
+
mod_path: fn_path,
|
181
|
+
mod_type: csv_row_val(row, :mod_type)
|
182
|
+
)
|
183
|
+
|
184
|
+
fn_info = ChaosDetector::Stacker::FnInfo.new(
|
185
|
+
fn_name: csv_row_val(row, :fn_name),
|
186
|
+
fn_line: fn_line,
|
187
|
+
fn_path: fn_path
|
188
|
+
)
|
189
|
+
|
190
|
+
caller_info = ChaosUtils.with(csv_row_val(row, :caller_type)) do |caller_type|
|
191
|
+
if caller_type.to_sym == :function
|
192
|
+
ChaosDetector::Stacker::FnInfo.new(
|
193
|
+
fn_name: csv_row_val(row, :caller_name),
|
194
|
+
fn_line: csv_row_val(row, :caller_info)&.to_i,
|
195
|
+
fn_path: csv_row_val(row, :caller_path)
|
196
|
+
)
|
197
|
+
else
|
198
|
+
ChaosDetector::Stacker::ModInfo.new(
|
199
|
+
mod_name: csv_row_val(row, :caller_name),
|
200
|
+
mod_path: csv_row_val(row, :caller_path),
|
201
|
+
mod_type: csv_row_val(row, :caller_info)
|
202
|
+
)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
ChaosDetector::Stacker::Frame.new(
|
207
|
+
event: event.to_sym,
|
208
|
+
mod_info: mod_info,
|
209
|
+
fn_info: fn_info,
|
210
|
+
caller_info: caller_info
|
211
|
+
)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|