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,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 560bb0eb60cbfb4a982c2189aba4a952eda085864e134dd328f98a0e3e4c80ee
4
+ data.tar.gz: 31042b5f09a591c43bdcfbafb984a216feafa989dcc9c7355e1c7d00d00aed8a
5
+ SHA512:
6
+ metadata.gz: e5b1e511011a833c3ce5ca72c59ea2dd38dd029197667a7ba8b1af024d4e681fc9ecf78ebf242ad8671f7d45f7063fa4193eec5dea587a3026d48fb8c84d209f
7
+ data.tar.gz: 35ea321082b59f7efeb717bf878168be956da718814984c9ecff1117f34e4c8bb125fb3dca02c8c37601fc6cc2012676ea9373c978ed220d624d41d2b530b106
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'thor'
3
+
4
+ require 'chaos_detector/navigator'
5
+ require 'chaos_detector/options'
6
+ require 'chaos_detector/chaos_utils'
7
+
8
+ module ChaosDetector
9
+ class DetectChaos < Thor
10
+ package_name 'DetectChaos'
11
+ desc 'playback', 'Loads dependencies saved by a previous test run'
12
+ opts = ChaosDetector::Options.new
13
+ method_option :opt_frame_csv, type: :string, default: opts.path_with_root(key: :frame_csv_path), required: true
14
+ method_option :opt_app_root, type: :string, default: opts.app_root_path, required: true
15
+ method_option :opt_module_filter, type: :string, default: 'ChaosDetector', required: true
16
+ # default_task :playback
17
+
18
+ def playback
19
+ ARGV.clear
20
+ opts = ChaosDetector::Options.new
21
+ chaos_nav = ChaosDetector::Navigator.new(options: opts)
22
+ ChaosUtils.log_msg("ChaosDetector::Navigator.playback with options: #{options}")
23
+ opts.frame_csv_path = options[:opt_frame_csv] # .sub(options[:opt_app_root], "")
24
+ opts.app_root_path = options[:opt_app_root]
25
+ opts.module_filter = options[:opt_module_filter]
26
+ chaos_nav.playback
27
+ end
28
+ end
29
+ end
30
+
31
+ ChaosDetector::DetectChaos.start(ARGV)
@@ -0,0 +1,22 @@
1
+ require 'chaos_detector/navigator'
2
+ require 'chaos_detector/graphing/directed_graphs'
3
+ require 'chaos_detector/options'
4
+
5
+ # module ChaosDetector
6
+ # # class << self
7
+ # # # Add struct or class to encapsulate include/exclude rules:
8
+ # # # include_paths:, exclude_paths:, include_classes:, exclude_classes:
9
+ # # def record(options=nil)#, include_paths:, exclude_paths:, include_classes:, exclude_classes:)
10
+
11
+ # # # puts(" Domains #{domain_hash.inspect}")
12
+ # # # # options.log_root_path = app_root_path
13
+ # # # puts(" log_root_path #{options.log_root_path}")
14
+ # # ChaosDetector::Navigator.record(options: options || ChaosDetector::Options.new)
15
+ # # end
16
+
17
+ # # def stop
18
+ # # ChaosDetector::Navigator.stop
19
+ # # end
20
+
21
+ # # end
22
+ # end
@@ -0,0 +1,32 @@
1
+ require_relative 'function_node'
2
+ require_relative 'domain_node'
3
+ require_relative 'module_node'
4
+
5
+ require 'chaos_detector/graph_theory/edge'
6
+ require 'chaos_detector/graph_theory/graph'
7
+ require 'chaos_detector/chaos_utils'
8
+
9
+ # Edge with dependency-tracking attributes
10
+ module ChaosDetector
11
+ module ChaosGraphs
12
+ class ChaosEdge < GraphTheory::Edge
13
+ attr_reader :dep_type
14
+
15
+ # association
16
+ DEP_TYPES = %i[association generalization aggregation composition].freeze
17
+ def initialize(src_node, dep_node, dep_type: :association, reduction: nil)
18
+ super
19
+ @dep_type = dep_type
20
+ end
21
+
22
+ def to_s
23
+ m = ChaosUtils.decorate(super, clamp: :parens, suffix: ' ')
24
+ m << ChaosUtils.decorate(@dep_type, clamp: :parens)
25
+ end
26
+
27
+ def log(msg, **opts)
28
+ ChaosUtils.log_msg(msg, subject: 'ChaosEdge', **opts)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,389 @@
1
+ require_relative 'function_node'
2
+ require_relative 'domain_node'
3
+ require_relative 'module_node'
4
+
5
+ require 'chaos_detector/chaos_utils'
6
+ require 'chaos_detector/graph_theory/appraiser'
7
+ require 'chaos_detector/graph_theory/edge'
8
+ require 'chaos_detector/graph_theory/graph'
9
+ require 'chaos_detector/graph_theory/node'
10
+ require 'chaos_detector/graph_theory/reduction'
11
+
12
+ # Encapsulate and aggregates graphs for dependency tracking
13
+ # * Function directed graph
14
+ # * Module directed graph - derived from function graph
15
+ # * Domain directed graph - derived from function graph
16
+ module ChaosDetector
17
+ module ChaosGraphs
18
+ class ChaosGraph
19
+ NODE_TYPES = %i[function module domain].freeze
20
+ STATES = %i[initialized inferred].freeze
21
+
22
+ attr_reader :function_graph
23
+ attr_reader :mod_rel_graph
24
+ attr_reader :domain_nodes
25
+ attr_reader :module_nodes
26
+
27
+ attr_reader :domain_appraisal
28
+ attr_reader :function_appraisal
29
+ attr_reader :module_appraisal
30
+ attr_reader :domain_edges
31
+ attr_reader :module_edges
32
+ attr_reader :module_domain_edges
33
+ attr_reader :domain_module_edges
34
+ attr_reader :function_domain_edges
35
+ attr_reader :domain_function_edges
36
+
37
+ def initialize(function_graph, mod_rel_graph)
38
+ @function_graph = function_graph
39
+ @mod_rel_graph = mod_rel_graph
40
+ @domain_nodes = nil
41
+ @module_nodes = nil
42
+
43
+ @domain_edges = nil
44
+ @module_edges = nil
45
+ @module_domain_edges = nil
46
+ @domain_module_edges = nil
47
+ @function_domain_edges = nil
48
+ @domain_function_edges = nil
49
+ end
50
+
51
+ def infer_all
52
+ assert_state
53
+ infer_module_nodes
54
+ infer_domain_nodes
55
+ prepare_root_nodes
56
+
57
+ # Now infer all edges:
58
+ infer_edges
59
+
60
+ # Graph theory appraisal
61
+ appraise_all
62
+ self
63
+ # @domain_graph = build_domain_graph(@domain_edges)
64
+ # @module_graph = build_domain_graph(@module_edges)
65
+ end
66
+
67
+ # ChaosDetector::ChaosGraphs::ChaosGraph.NODE_TYPES
68
+ def derive_graph(graph_type:, sort_col: :total_couplings, include_root: true, sort: :desc, top: nil)
69
+ sortcol = sort_col || :total_couplings
70
+ graph, appraisal = graph_data_for(graph_type: graph_type)
71
+
72
+ nodes = graph.nodes(include_root: false)
73
+ # nodes.filter!{ |node| yield(node) } if block_given?
74
+
75
+ # Use appraisal metrics for sorting:
76
+ node_metrics = nodes.map{|node| appraisal.metrics_for(node: node)}
77
+ n_sort = node_metrics.map{|m| m.send(sortcol)}.map.with_index.sort.map(&:last)
78
+ n_sort.reverse! if sort == :desc
79
+
80
+ # Limit:
81
+ if top
82
+ ChaosUtils.with(top.to_i) do |t|
83
+ n_sort = n_sort.take(t) if t.positive?
84
+ end
85
+ end
86
+
87
+
88
+ gnodes = n_sort.map{|i| nodes[i].clone }
89
+
90
+ # gnodes = new_nodes.map(&:clone)
91
+ gedges = graph.edges.filter_map do |edge|
92
+ src_node = gnodes.find{ |node| node == edge.src_node }
93
+ dep_node = gnodes.find{ |node| node == edge.dep_node }
94
+ if src_node && dep_node
95
+ edge.dup.tap do |gedge|
96
+ gedge.src_node = src_node
97
+ gedge.dep_node = dep_node
98
+ end
99
+ end
100
+ end
101
+
102
+ ChaosDetector::GraphTheory::Graph.new(
103
+ root_node: include_root ? gnodes.find(&:is_root)&.dup : nil,
104
+ nodes: gnodes,
105
+ edges: gedges
106
+ )
107
+ end
108
+
109
+ def domain_graph
110
+ assert_state(:inferred)
111
+ @domain_graph ||= build_domain_graph(edges: @domain_edges)
112
+ end
113
+
114
+ def module_graph
115
+ assert_state(:inferred)
116
+ @module_graph ||= build_module_graph(edges: @module_edges)
117
+ end
118
+
119
+ # Lookup domain node by name:
120
+ def domain_node_for(name:)
121
+ # domain_nodes.find(->{root_node_domain}){|node| node.name.to_s == name.to_s}
122
+ domain_nodes.find(->{root_node_domain}){|node| node.name.to_s == name.to_s}
123
+ end
124
+
125
+ ## Derive domain-level graph from function-based graph
126
+ def build_domain_graph(edges: @domain_edes)
127
+ assert_state
128
+ ChaosDetector::GraphTheory::Graph.new(root_node: root_node_domain, nodes: @domain_nodes, edges: edges)
129
+ end
130
+
131
+ ## Derive module-level graph from function-based graph
132
+ def build_module_graph(edges: @module_edges)
133
+ assert_state
134
+ ChaosDetector::GraphTheory::Graph.new(root_node: root_node_module, nodes: @module_nodes, edges: edges)
135
+ end
136
+
137
+ ## Return [graph, appraisal] for given type:
138
+ def graph_data_for(graph_type:)
139
+ assert_state(:inferred)
140
+
141
+ case graph_type
142
+ when :function
143
+ [function_graph, function_appraisal]
144
+ when :module
145
+ [module_graph, module_appraisal]
146
+ when :domain
147
+ [domain_graph, domain_appraisal]
148
+ else
149
+ raise "graph_type should be one of #{NODE_TYPES.inspect}, actual value: #{ChaosUtils.decorate(graph_type)}"
150
+ end
151
+ end
152
+
153
+ # Use Graph theory to appraise given graph:
154
+ def appraise_graph(graph, sort_col: :total_couplings, sort_desc: true, top: nil)
155
+ appraiser = ChaosDetector::GraphTheory::Appraiser.new(graph)
156
+ appraiser.appraise
157
+ appraiser
158
+ end
159
+
160
+
161
+ private
162
+
163
+ # Graph theory appraisal
164
+ def appraise_all
165
+ log("Appraising graphs")
166
+ @domain_appraisal = appraise_graph(domain_graph)
167
+ @module_appraisal = appraise_graph(module_graph)
168
+ @function_appraisal = appraise_graph(function_graph)
169
+ end
170
+
171
+ def assert_state(state = nil)
172
+ raise "function_graph.nodes isn't set!" unless function_graph&.nodes
173
+
174
+ if state == :inferred
175
+ raise "@domain_nodes isn't set; call #build" unless @domain_nodes
176
+ raise "@module_nodes isn't set; call #build" unless @module_nodes
177
+ end
178
+ end
179
+
180
+ def prepare_root_nodes
181
+ assert_state(:inferred)
182
+ ChaosUtils.with(root_node_function) do |fn_root_node|
183
+ @function_graph.nodes.unshift(fn_root_node) unless @function_graph.nodes.include?(fn_root_node)
184
+ end
185
+
186
+ ChaosUtils.with(root_node_domain) do |domain_root_node|
187
+ @domain_nodes.unshift(domain_root_node) unless @domain_nodes.include?(domain_root_node)
188
+ end
189
+
190
+ ChaosUtils.with(root_node_module) do |mod_root_node|
191
+ @module_nodes.unshift(mod_root_node) unless @module_nodes.include?(mod_root_node)
192
+ end
193
+ end
194
+
195
+ def root_node_function
196
+ assert_state
197
+ root_node = @function_graph.nodes.find(&:is_root)
198
+ root_node || ChaosDetector::ChaosGraphs::FunctionNode.root_node
199
+ end
200
+
201
+ def root_node_domain
202
+ assert_state(:inferred)
203
+ root_node = @domain_nodes.find(&:is_root)
204
+ root_node || ChaosDetector::ChaosGraphs::DomainNode.root_node
205
+ end
206
+
207
+ def root_node_module
208
+ assert_state(:inferred)
209
+
210
+ root_node = @module_nodes.find(&:is_root)
211
+ root_node || ChaosDetector::ChaosGraphs::ModuleNode.root_node
212
+ end
213
+
214
+ def infer_module_nodes
215
+ assert_state
216
+
217
+ grouped_nodes = @function_graph.nodes.group_by(&:mod_info_prime)
218
+
219
+ # mod_nodes = grouped_nodes.select do |mod_info, _fn_nodes|
220
+ # ChaosUtils.aught?(mod_info&.mod_name)
221
+ # end
222
+
223
+ mod_nodes = grouped_nodes.filter_map do |mod_info, fn_nodes|
224
+ next unless ChaosUtils.aught?(mod_info&.mod_name)
225
+
226
+ node_fn = fn_nodes.first
227
+
228
+ fn_reductions = fn_nodes.map(&:reduction)
229
+
230
+ mod_reduction = ChaosDetector::GraphTheory::Reduction.combine_all(fn_reductions)
231
+ # puts ("mod_reduction: %s" % mod_reduction.inspect)
232
+
233
+ ChaosDetector::ChaosGraphs::ModuleNode.new(
234
+ mod_name: mod_info.mod_name,
235
+ mod_type: mod_info.mod_type,
236
+ mod_path: mod_info.mod_path,
237
+ domain_name: node_fn.domain_name,
238
+ reduction: mod_reduction
239
+ )
240
+ end
241
+
242
+ mod_nodes.uniq!
243
+
244
+ @mod_rel_graph.nodes.each do |rel_node|
245
+ n = mod_nodes.index(rel_node)
246
+ mod_nodes << rel_node if n.nil?
247
+ end
248
+
249
+ @module_nodes = mod_nodes.uniq
250
+ end
251
+
252
+ def infer_domain_nodes
253
+ assert_state
254
+
255
+ @domain_nodes = @module_nodes.group_by(&:domain_name).map do |dom_nm, mod_nodes|
256
+ mod_reductions = mod_nodes.map(&:reduction)
257
+ dom_reduction = ChaosDetector::GraphTheory::Reduction.combine_all(mod_reductions)
258
+ ChaosDetector::ChaosGraphs::DomainNode.new(
259
+ domain_name: dom_nm,
260
+ reduction: dom_reduction,
261
+ is_root: dom_nm==ChaosDetector::GraphTheory::Node::ROOT_NODE_NAME || !ChaosUtils.aught?(dom_nm)
262
+ )
263
+ end
264
+ end
265
+
266
+ def infer_edges
267
+ assert_state
268
+
269
+ fn_edges = @function_graph.edges
270
+ mod_edges = group_edges_by(fn_edges, :module, :module).concat(@mod_rel_graph.edges)
271
+ check_edges("mod_edges", mod_edges)
272
+
273
+ dom_edges = group_edges_by(mod_edges, :domain, :domain)
274
+ check_edges("dom_edges", dom_edges)
275
+
276
+ @domain_edges = reduce_edges(dom_edges)
277
+ check_edges("@domain_edges", @domain_edges)
278
+
279
+ @module_edges = reduce_edges(mod_edges)
280
+ check_edges("@module_edges", @module_edges)
281
+ end
282
+
283
+ def check_edges(name, edges)
284
+ return
285
+ edges.each do |edge|
286
+ puts("#{name} EDGE: #{edge.class} / #{edge.reduction.class} / #{edge}")
287
+ end
288
+ end
289
+
290
+ def group_edges_by(edges, src, dep)
291
+ assert_state
292
+ raise ArgumentError, 'edges argument required' unless edges
293
+
294
+ # log("GROUPING EDGES by #{src} and #{dep}")
295
+
296
+ groupedges = edges.group_by do |e|
297
+ [
298
+ node_group_prop(e.src_node, node_type: src),
299
+ node_group_prop(e.dep_node, node_type: dep)
300
+ ]
301
+ end
302
+
303
+ # valid_edges = groupedges.select do |src_dep_pair, g_edges|
304
+ # src_dep_pair.all?
305
+ # end
306
+
307
+ groupedges.filter_map do |src_dep_pair, g_edges|
308
+ raise 'Pair should have two exactly items.' unless src_dep_pair.length == 2
309
+
310
+ # log("Looking up pair: #{src_dep_pair.inspect}")
311
+ edge_src_node = lookup_node_by(node_type: src, node_info: src_dep_pair.first)
312
+ edge_dep_node = lookup_node_by(node_type: dep, node_info: src_dep_pair.last)
313
+
314
+ # log("Creating #{src_dep_pair.first.class} edge with #{ChaosUtils.decorate_pair(edge_src_node, edge_dep_node)}")
315
+ if (edge_src_node != edge_dep_node)
316
+ ChaosDetector::GraphTheory::Edge.new(
317
+ edge_src_node,
318
+ edge_dep_node,
319
+ reduction: ChaosDetector::GraphTheory::Reduction.new(
320
+ reduction_count: g_edges.count,
321
+ reduction_sum: g_edges.reduce(0) do |sum, e|
322
+ sum + (e.reduction&.reduction_count || 1)
323
+ end
324
+ )
325
+ )
326
+ end
327
+ end
328
+ end
329
+
330
+ def reduce_edges(edges)
331
+ edges.reduce(Set.new) do |memo, obj|
332
+ existing = memo.find{|m| obj==m}
333
+ if existing
334
+ existing.merge!(obj)
335
+ else
336
+ memo << obj
337
+ end
338
+ memo
339
+ end.to_a
340
+ end
341
+
342
+ def node_group_prop(node, node_type:)
343
+ unless NODE_TYPES.include? node_type
344
+ raise format('node_type should be one of symbols in %s, actual value: %s (%s)', NODE_TYPES.inspect, ChaosUtils.decorate(node_type), ChaosUtils.decorate(node_type.class))
345
+ end
346
+
347
+ case node_type
348
+ when :function
349
+ node.to_info
350
+ when :module
351
+ node.mod_info_prime
352
+ when :domain
353
+ node.domain_name
354
+ end
355
+ end
356
+
357
+ def lookup_node_by(node_type:, node_info:)
358
+ assert_state
359
+
360
+ # It is already a node:
361
+ return node_info if node_info.is_a? ChaosDetector::GraphTheory::Node
362
+
363
+ case node_type
364
+ when :function
365
+ # Look up by FnInfo
366
+ n = node_info && @function_graph.nodes.index(node_info)
367
+ n.nil? ? root_node_function : @function_graph.nodes[n]
368
+ when :module
369
+ # Look up by module info
370
+ n = node_info && @module_nodes.index(node_info)
371
+ n.nil? ? root_node_module : @module_nodes[n]
372
+ when :domain
373
+ # Look up by Domain name
374
+ unless (node_info.nil? || node_info.is_a?(String) || node_info.is_a?(Symbol))
375
+ log("NodeInfo is something other than info or string type: class: (#{node_info.class}) = #{node_info.inspect}")
376
+ end
377
+
378
+ domain_node_for(name: node_info.to_s) || root_node_domain
379
+ else
380
+ raise "node_type should be one of #{NODE_TYPES.inspect}, actual value: #{ChaosUtils.decorate(node_type)}"
381
+ end
382
+ end
383
+
384
+ def log(msg, **opts)
385
+ ChaosUtils.log_msg(msg, subject: 'ChaosGraph', **opts)
386
+ end
387
+ end
388
+ end
389
+ end