chaos_detector 0.4.9

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