chaos_detector 0.4.9
Sign up to get free protection for your applications and to get access to all the features.
- 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
|