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,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