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.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/bin/detect_chaos +31 -0
  3. data/lib/chaos_detector.rb +22 -0
  4. data/lib/chaos_detector/chaos_graphs/chaos_edge.rb +32 -0
  5. data/lib/chaos_detector/chaos_graphs/chaos_graph.rb +389 -0
  6. data/lib/chaos_detector/chaos_graphs/domain_metrics.rb +19 -0
  7. data/lib/chaos_detector/chaos_graphs/domain_node.rb +57 -0
  8. data/lib/chaos_detector/chaos_graphs/function_node.rb +112 -0
  9. data/lib/chaos_detector/chaos_graphs/module_node.rb +86 -0
  10. data/lib/chaos_detector/chaos_utils.rb +57 -0
  11. data/lib/chaos_detector/graph_theory/appraiser.rb +162 -0
  12. data/lib/chaos_detector/graph_theory/edge.rb +76 -0
  13. data/lib/chaos_detector/graph_theory/graph.rb +144 -0
  14. data/lib/chaos_detector/graph_theory/loop_detector.rb +32 -0
  15. data/lib/chaos_detector/graph_theory/node.rb +70 -0
  16. data/lib/chaos_detector/graph_theory/node_metrics.rb +68 -0
  17. data/lib/chaos_detector/graph_theory/reduction.rb +40 -0
  18. data/lib/chaos_detector/graphing/directed_graphs.rb +396 -0
  19. data/lib/chaos_detector/graphing/graphs.rb +129 -0
  20. data/lib/chaos_detector/graphing/matrix_graphs.rb +101 -0
  21. data/lib/chaos_detector/navigator.rb +237 -0
  22. data/lib/chaos_detector/options.rb +51 -0
  23. data/lib/chaos_detector/stacker/comp_info.rb +42 -0
  24. data/lib/chaos_detector/stacker/fn_info.rb +44 -0
  25. data/lib/chaos_detector/stacker/frame.rb +34 -0
  26. data/lib/chaos_detector/stacker/frame_stack.rb +63 -0
  27. data/lib/chaos_detector/stacker/mod_info.rb +24 -0
  28. data/lib/chaos_detector/tracker.rb +276 -0
  29. data/lib/chaos_detector/utils/core_util.rb +117 -0
  30. data/lib/chaos_detector/utils/fs_util.rb +49 -0
  31. data/lib/chaos_detector/utils/lerp_util.rb +20 -0
  32. data/lib/chaos_detector/utils/log_util.rb +45 -0
  33. data/lib/chaos_detector/utils/str_util.rb +90 -0
  34. data/lib/chaos_detector/utils/tensor_util.rb +21 -0
  35. data/lib/chaos_detector/walkman.rb +214 -0
  36. metadata +147 -0
@@ -0,0 +1,129 @@
1
+ require 'chaos_detector/options'
2
+ require 'chaos_detector/navigator'
3
+ require 'chaos_detector/stacker/frame'
4
+ require 'chaos_detector/graph_theory/appraiser'
5
+ require 'chaos_detector/graph_theory/graph'
6
+ require 'chaos_detector/graphing/directed_graphs'
7
+ require 'chaos_detector/chaos_graphs/chaos_graph'
8
+ require 'chaos_detector/utils/str_util'
9
+ require 'chaos_detector/chaos_utils'
10
+
11
+ require_relative 'directed_graphs'
12
+ require_relative 'matrix_graphs'
13
+
14
+ module ChaosDetector
15
+ module Graphing
16
+ ### Top-level Chaos-detection graphing and rendering
17
+ class Graphs
18
+ attr_reader :navigator
19
+ attr_reader :chaos_graph
20
+
21
+ def initialize(options: nil)
22
+ @options = options || ChaosDetector::Options.new
23
+ @render_folder = @options.path_with_root(key: :graph_render_folder)
24
+ # @render_folder = @options.graph_render_folder
25
+ ChaosDetector::Utils::FSUtil.ensure_dirpath(@render_folder)
26
+ @navigator = ChaosDetector::Navigator.new(options: @options)
27
+ end
28
+
29
+ def playback(row_range: nil)
30
+ fn_graph, mod_graph = @navigator.playback(row_range: row_range)
31
+ @chaos_graph = ChaosDetector::ChaosGraphs::ChaosGraph.new(fn_graph, mod_graph)
32
+ @chaos_graph.infer_all
33
+ end
34
+
35
+ GRAPH_TYPE_ATTRS = {
36
+ domain: {
37
+ # ratio: 'auto',
38
+ # size: '8, 8',
39
+ # rankdir: 'TB',
40
+ # packmode: 'clust'
41
+ },
42
+ function: {
43
+
44
+ },
45
+ module: {
46
+ rankdir: 'TB',
47
+ packmode: 'clust',
48
+ ranksep: '1.5',
49
+ },
50
+ }
51
+
52
+ def adjacency_graph(graph_type, graph: nil, graph_name: 'adj-matrix')
53
+ the_matrix = ChaosDetector::Graphing::MatrixGraphs.new(chaos_graph, render_folder: @render_folder)
54
+ graph, appraiser = @chaos_graph.graph_data_for(graph_type: graph_type)
55
+ matrix = appraiser.adjacency_matrix
56
+ the_matrix.render_adjacency(matrix, graph_name: graph_name)
57
+ end
58
+
59
+ def render_dep_graph(graph_type, graph: nil, as_cluster: false, domains: false, name: nil, root: true, metrics_table: false)
60
+ g, _appraiser = chaos_graph.graph_data_for(graph_type: graph_type)
61
+ rgraph = graph ? graph : g
62
+ graph_name = name ? name : "#{graph_type}-dep"
63
+
64
+ graph_attrs = GRAPH_TYPE_ATTRS[graph_type]
65
+
66
+ dgraph = if domains #&& graph_type != :cluster
67
+ build_domain_dgraph(graph_name, rgraph.nodes, rgraph.edges, graph_attrs: graph_attrs, metrics_table: metrics_table)
68
+ else
69
+ build_dgraph(graph_name, rgraph.nodes, rgraph.edges, as_cluster: as_cluster, graph_attrs: graph_attrs, metrics_table: metrics_table)
70
+ end
71
+
72
+ dgraph.rendered_path
73
+ end
74
+
75
+ def render_domain_dep(graph_name: 'domain-dep', domain_graph: nil, metrics_table: false)
76
+ render_dep_graph(:domain, as_cluster: true, graph: domain_graph, name: graph_name, metrics_table: metrics_table)
77
+ end
78
+
79
+ def render_fn_dep(graph_name: 'fn-dep', function_graph: nil, domains: false, metrics_table: false)
80
+ render_dep_graph(:function, as_cluster: true, graph: function_graph, domains: domains, name: graph_name, metrics_table: metrics_table)
81
+ end
82
+
83
+ def render_mod_dep(graph_name: 'module-dep', module_graph: nil, domains: false, metrics_table: false)
84
+ render_dep_graph(:module, graph: module_graph, domains: domains, name: graph_name, metrics_table: metrics_table)
85
+ end
86
+
87
+ private
88
+
89
+ def build_dgraph(label, nodes, edges, as_cluster: false, render: true, graph_attrs: nil, metrics_table: false)
90
+ # nodes.each do |n|
91
+ # p("#{label} Nodes: #{ChaosUtils.decorate(n.title)}")
92
+ # end
93
+
94
+ # edges.each do |e|
95
+ # p("#{label} Edges: #{ChaosUtils.decorate(e.src_node.title)} -> #{ChaosUtils.decorate(e.dep_node.title)}")
96
+ # end
97
+ puts("Building #{label} with as_cluster: #{as_cluster}")
98
+ dgraph = ChaosDetector::Graphing::DirectedGraphs.new(render_folder: @render_folder)
99
+ dgraph.create_directed_graph(label, graph_attrs: graph_attrs)
100
+ dgraph.append_nodes(nodes, as_cluster: as_cluster, metrics_table: metrics_table)
101
+ dgraph.add_edges(edges)
102
+ dgraph.render_graph if render
103
+ dgraph
104
+ end
105
+
106
+ def build_domain_dgraph(graph_name, nodes, edges, render: true, graph_attrs: nil, metrics_table: false)
107
+ # Add domains as cluster/subgraph nodes:
108
+ domain_nodes = nodes.map{|node| chaos_graph.domain_node_for(name: node.domain_name) }
109
+ dgraph = build_dgraph(graph_name, domain_nodes, [], as_cluster: true, render: false, graph_attrs: graph_attrs, metrics_table: metrics_table)
110
+
111
+ # Add nodes to domains:
112
+ dgraph.append_nodes(nodes, metrics_table: false) do |node|
113
+ chaos_graph.domain_node_for(name: node.domain_name)
114
+ end
115
+ dgraph.add_edges(edges)
116
+ dgraph.render_graph if render
117
+ dgraph
118
+ # dgraph.rendered_path
119
+
120
+ # fn_nodes = chaos_graph.function_graph.nodes.group_by(&:domain_name)
121
+ # fn_nodes.map do |dom_nm, fn_nodes|
122
+ # dom_node = ChaosUtils.aught?(dom_nm) && chaos_graph.domain_node_for(name: dom_nm)
123
+ # dgraph.add_node_to_parent(fn_node, dom_node)
124
+ # end
125
+ end
126
+
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,101 @@
1
+ require 'set'
2
+ require 'ruby-graphviz'
3
+ require 'rubyvis'
4
+
5
+ require 'chaos_detector/utils/str_util'
6
+ require 'chaos_detector/chaos_utils'
7
+
8
+ module ChaosDetector
9
+ module Graphing
10
+ class MatrixGraphs
11
+ CELL_WIDTH = 100
12
+ NodeStruct = Struct.new(:node_name, :node_value, :group, :index, :link_degree)
13
+ LinkNodeStruct = Struct.new(:index, :link_degree)
14
+ LinkStruct = Struct.new(:source, :target, :source_node, :target_node, :value, :link_value, :link_degree)
15
+
16
+ def initialize(chaos_graph, render_folder: nil)
17
+ @chaos_graph = chaos_graph
18
+ @render_folder = render_folder || 'render'
19
+ end
20
+
21
+ def simple_graph_struct(graph)
22
+ groupset = graph.nodes.reduce(Set.new()) { |memo, n| memo.add(n.domain_name) }
23
+ groups = groupset.to_a.sort!
24
+
25
+ nodes = graph.nodes.map.with_index do |node, n|
26
+ group_index = groups.index(node.domain_name)
27
+ NodeStruct.new(node.title, node.to_k, group_index, n)
28
+ end
29
+
30
+ links = graph.edges.map do |edge|
31
+ w = edge.weight
32
+ src = nodes.index(edge.src_node)
33
+ dep = nodes.index(edge.dep_node)
34
+ LinkStruct.new(src, dep, LinkNodeStruct.new(0, w), LinkNodeStruct.new(0, w), w, w )
35
+ end
36
+
37
+ [nodes, links]
38
+ end
39
+
40
+ def render_adjacency(matrix, graph_name: 'adj-matrix')
41
+
42
+ puts "MATRIX COUNT: #{matrix.row_size} x #{matrix.column_size} = #{matrix.count}"
43
+ matrix.row_vectors.each{|v| puts v.to_a.inspect}
44
+
45
+ # simple_nodes, simple_links = simple_graph_struct(graph)
46
+
47
+ w = Math.sqrt(matrix.count) * CELL_WIDTH
48
+ h = w
49
+
50
+ color=Rubyvis::Colors.category19
51
+
52
+ vis = Rubyvis::Panel.new() do
53
+ width w
54
+ height h
55
+ top 90
56
+ left 90
57
+
58
+ end
59
+
60
+ # layout_matrix do
61
+ # nodes simple_nodes
62
+ # links simple_links
63
+ # # sort {|a,b| b.group<=>a.group }
64
+ # directed (true)
65
+ # link.bar do
66
+ # fill_style {|l| l.link_value!=0 ?
67
+ # ((l.target_node.group == l.source_node.group) ? color[l.source_node.group] : "#555") : "#eee"}
68
+ # antialias(false)
69
+ # line_width(1)
70
+ # end
71
+ # node_label.label do
72
+ # text_style {|l| color[l.group]}
73
+ # end
74
+ # end
75
+
76
+ # vis
77
+ # .add(Rubyvis::Layout::Grid)
78
+ # .rows(matrix.to_a)
79
+ # .cell
80
+ # .add(Rubyvis::Bar)
81
+ # .fill_style(Rubyvis.ramp("white", "black"))
82
+ # .anchor("center").
83
+ # .add(Rubyvis::Label)
84
+ # .text_style(Rubyvis.ramp("black","white"))
85
+ # .text(lambda{|v| v.is_a?(Numeric) ? ("%0.2f" % v) : v.to_s})
86
+
87
+ vis.render();
88
+ svg = vis.to_svg()
89
+
90
+ @rendered_path = File.join(@render_folder, "#{graph_name}.svg").to_s
91
+ ChaosDetector::Utils::FSUtil.safe_file_write(@rendered_path, content: svg)
92
+ @rendered_path
93
+ end
94
+
95
+ private
96
+ def log(msg, **opts)
97
+ ChaosUtils.log_msg(msg, subject: 'MatrixDiagram', **opts)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,237 @@
1
+ require 'pathname'
2
+ require_relative 'options'
3
+ require_relative 'stacker/mod_info'
4
+ require_relative 'graph_theory/graph'
5
+ require_relative 'chaos_graphs/function_node'
6
+ require_relative 'chaos_graphs/module_node'
7
+ require_relative 'stacker/frame'
8
+ require_relative 'walkman'
9
+ require 'chaos_detector/chaos_utils'
10
+
11
+ # The main interface for intercepting tracepoints,
12
+ # and converting them into recordable and playable
13
+ # stack/trace frames
14
+
15
+ module ChaosDetector
16
+ class Navigator
17
+ REGEX_MODULE_UNDECORATE = /#<(Class:)?([a-zA-Z\:]*)(.*)>/.freeze
18
+ DEFAULT_GROUP = 'default'.freeze
19
+ FRAME_ACTIONS = [:return].freeze # , :return, :class, :end]
20
+
21
+ attr_reader :options
22
+ attr_reader :domain_hash
23
+ attr_reader :walkman
24
+
25
+ attr_reader :nodes
26
+ attr_reader :edges
27
+
28
+ attr_reader :mod_nodes
29
+ attr_reader :mod_edges
30
+
31
+ def initialize(options:)
32
+ raise ArgumentError, '#initialize requires options' if options.nil?
33
+
34
+ @options = options
35
+ apply_options
36
+ end
37
+
38
+ ### Playback of walkman CSV file:
39
+ def playback(row_range: nil)
40
+ log('Chaos playing through navigator. Total lines: ', object: @walkman.count)
41
+
42
+ @nodes = Set.new
43
+ @edges_call = Set.new
44
+ @edges_ret = Set.new
45
+ @mod_nodes = Set.new
46
+ @mod_edges = Set.new
47
+ @err_nodes = Set.new
48
+
49
+ @walkman.playback(row_range: row_range) do |_rownum, frame|
50
+ perform_node_action(frame)
51
+ end
52
+ # log('Found nodes.', object: @nodes.length)
53
+
54
+ @walkman.playback(row_range: row_range) do |_rownum, frame|
55
+ if [:call, :return].include?(frame.event)
56
+ perform_edge_action(frame)
57
+ else
58
+ perform_mod_edge_action(frame)
59
+ end
60
+ end
61
+
62
+ edges = merge_edges.to_a
63
+ # log('Found edges.', object: edges.length)
64
+
65
+ @mod_graph = ChaosDetector::GraphTheory::Graph.new(
66
+ root_node: ChaosDetector::ChaosGraphs::ModuleNode.root_node(force_new: true),
67
+ nodes: @mod_nodes.to_a,
68
+ edges: @mod_edges.to_a
69
+ )
70
+
71
+ @fn_graph = ChaosDetector::GraphTheory::Graph.new(
72
+ root_node: ChaosDetector::ChaosGraphs::FunctionNode.root_node(force_new: true),
73
+ nodes: @nodes.to_a,
74
+ edges: edges
75
+ )
76
+
77
+ [@fn_graph, @mod_graph]
78
+ end
79
+
80
+ private
81
+
82
+ def apply_options
83
+ @walkman = ChaosDetector::Walkman.new(options: @options)
84
+ @domain_hash = {}
85
+ # @options.path_domain_hash && options.path_domain_hash.each do |path, group|
86
+ # dpath = Pathname.new(path.to_s).cleanpath.to_s
87
+ # @domain_hash[dpath] = group
88
+ # end
89
+ end
90
+
91
+ # We merge/reduce edges elsewhere:
92
+ def merge_edges
93
+ c = Set.new(@edges_call)
94
+ r = Set.new(@edges_ret)
95
+
96
+ raise 'Call Edges should be Set' unless c.length == @edges_call.length
97
+ raise 'Ret Edges should be Set' unless r.length == @edges_ret.length
98
+
99
+ raise 'Call Edges should be unique' unless @edges_call.uniq.length == @edges_call.length
100
+ raise 'Call Edges should be unique' unless @edges_ret.uniq.length == @edges_ret.length
101
+
102
+ # log('Unique edges in call (n/total)', object: [(c - r).length, c.length])
103
+ # log('Unique edges in return (n/total)', object: [(r - c).length, r.length])
104
+
105
+ # @edges_call.each do |e|
106
+ # log("edges_call", object: e)
107
+ # end
108
+
109
+ # @edges_ret.each do |e|
110
+ # log("edges_ret ", object: e)
111
+ # end
112
+
113
+ c.union(r)
114
+ end
115
+
116
+ def fn_node_for(fn_info)
117
+ return nil unless fn_info&.fn_name
118
+
119
+ @nodes.find do |n|
120
+ n.fn_name == fn_info.fn_name &&
121
+ n.fn_path == fn_info.fn_path
122
+ end
123
+ end
124
+
125
+ def mod_node_for(mod_info)
126
+ return nil unless mod_info&.mod_name
127
+
128
+ @mod_nodes.find do |n|
129
+ n.mod_name == mod_info.mod_name &&
130
+ n.mod_path == mod_info.mod_path
131
+ end
132
+ end
133
+
134
+ # @return Node matching given frame or create a new one.
135
+ def fn_node_for_frame(frame)
136
+ # log("Calling fn_node_for_frame", object: frame)
137
+ node = fn_node_for(frame.fn_info)
138
+
139
+ if node.nil? && frame.event == :call
140
+ fn_info = frame.fn_info
141
+ node = ChaosDetector::ChaosGraphs::FunctionNode.new(
142
+ fn_name: fn_info.fn_name,
143
+ fn_path: fn_info.fn_path,
144
+ fn_line: fn_info.fn_line,
145
+ domain_name: options.domain_from_path(fn_info.fn_path),
146
+ mod_info: frame.mod_info
147
+ )
148
+ @nodes << node
149
+ end
150
+
151
+ node
152
+ end
153
+
154
+ def mod_node_from_info(mod_info)
155
+ # log("Calling fn_node_for_frame", object: frame)
156
+ node = mod_node_for(mod_info)
157
+
158
+ if node.nil? #&& frame.event == :call
159
+ node = ChaosDetector::ChaosGraphs::ModuleNode.new(
160
+ mod_name: mod_info.mod_name,
161
+ mod_path: mod_info.mod_path,
162
+ mod_type: mod_info.mod_type,
163
+ domain_name: options.domain_from_path(mod_info.mod_path)
164
+ )
165
+
166
+ @mod_nodes << node
167
+ end
168
+
169
+ node
170
+ end
171
+
172
+ def edge_for_nodes(src_node, dep_node, edges:, edge_type: :dependent)
173
+ edge = edges.find do |e|
174
+ e.src_node == src_node && e.dep_node == dep_node
175
+ end
176
+ if edge.nil?
177
+ edge = ChaosDetector::GraphTheory::Edge.new(src_node, dep_node, edge_type: edge_type)
178
+ edges << edge
179
+ end
180
+ edge
181
+ end
182
+
183
+ def perform_node_action(frame)
184
+ return unless [:call, :return].include?(frame.event)
185
+ node = fn_node_for_frame(frame)
186
+
187
+ ChaosUtils.with(node && frame.event == :return && frame.fn_info.fn_line) do |fn_line|
188
+ if !node.fn_line_end.nil? && node.fn_line_end != fn_line
189
+ end
190
+ node.fn_line_end = [fn_line, node.fn_line_end.to_i].max
191
+ end
192
+ end
193
+
194
+
195
+ def perform_edge_action(frame)
196
+ return unless frame.fn_info && frame.event==:call #&& frame.caller_info
197
+
198
+ dest_node = fn_node_for(frame.fn_info)
199
+ if dest_node.nil?
200
+ # unless @err_nodes.include?(dest_node)
201
+ # log "Couldn't find destination node (of #{@nodes.length} / #{@edges_call.length}) on #{frame}"
202
+ # @err_nodes << dest_node
203
+ # end
204
+ return
205
+ end
206
+
207
+ caller_node = fn_node_for(frame.caller_info)
208
+ if caller_node.nil?
209
+ caller_node = ChaosDetector::ChaosGraphs::FunctionNode.root_node
210
+ raise 'Caller node is required (falls back to root).' if caller_node.nil?
211
+
212
+ # log("Adding edge to root!")
213
+ @nodes << caller_node
214
+ end
215
+
216
+ edges = frame.event == :return ? @edges_ret : @edges_call
217
+ edge_for_nodes(caller_node, dest_node, edges: edges)
218
+ end
219
+
220
+ def perform_mod_edge_action(frame)
221
+ return unless frame.mod_info && frame.caller_info
222
+
223
+ caller_node = mod_node_from_info(frame.caller_info)
224
+ dest_node = mod_node_from_info(frame.mod_info)
225
+ edge_for_nodes(dest_node, caller_node, edges: @mod_edges, edge_type: frame.event)
226
+ end
227
+
228
+ def domain_from_path(local_path:)
229
+ key = domain_hash.keys.find { |k| local_path.start_with?(k) }
230
+ key ? domain_hash[key] : ChaosDetector::GraphTheory::Node::ROOT_NODE_NAME
231
+ end
232
+
233
+ def log(msg, **opts)
234
+ ChaosUtils.log_msg(msg, subject: 'Navigator', **opts)
235
+ end
236
+ end
237
+ end