utilrb 1.6.6 → 2.0

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 (51) hide show
  1. data/Manifest.txt +10 -9
  2. data/README.rd +23 -0
  3. data/Rakefile +40 -37
  4. data/ext/utilrb/extconf.rb +36 -0
  5. data/ext/{proc.cc → utilrb/proc.c} +7 -6
  6. data/ext/utilrb/ruby_allocator.hh +76 -0
  7. data/ext/{ruby_internals-1.8.h → utilrb/ruby_internals-1.8.h} +0 -0
  8. data/ext/{ruby_internals-1.9.h → utilrb/ruby_internals-1.9.h} +0 -0
  9. data/ext/{swap.cc → utilrb/swap.cc} +0 -0
  10. data/ext/utilrb/utilrb.cc +79 -0
  11. data/ext/{value_set.cc → utilrb/value_set.cc} +5 -5
  12. data/ext/{weakref.cc → utilrb/weakref.cc} +0 -0
  13. data/lib/utilrb/common.rb +7 -2
  14. data/lib/utilrb/doc/rake.rb +46 -15
  15. data/lib/utilrb/kernel/load_dsl_file.rb +2 -2
  16. data/lib/utilrb/kernel/options.rb +6 -0
  17. data/lib/utilrb/logger/forward.rb +7 -0
  18. data/lib/utilrb/logger/hierarchy.rb +100 -42
  19. data/lib/utilrb/logger/indent.rb +38 -3
  20. data/lib/utilrb/logger/log_pp.rb +1 -1
  21. data/lib/utilrb/logger/root.rb +27 -12
  22. data/lib/utilrb/module/inherited_enumerable.rb +2 -196
  23. data/lib/utilrb/objectstats.rb +4 -1
  24. data/lib/utilrb/pkgconfig.rb +42 -6
  25. data/lib/utilrb/rake_common.rb +12 -0
  26. data/lib/utilrb/ruby_object_graph.rb +195 -46
  27. data/lib/utilrb/yard.rb +89 -89
  28. data/test/test_array.rb +1 -1
  29. data/test/test_dir.rb +1 -1
  30. data/test/test_enumerable.rb +7 -1
  31. data/test/test_event_loop.rb +407 -0
  32. data/test/test_exception.rb +1 -1
  33. data/test/test_gc.rb +1 -1
  34. data/test/test_hash.rb +57 -1
  35. data/test/test_kernel.rb +52 -21
  36. data/test/test_logger.rb +150 -1
  37. data/test/test_misc.rb +1 -1
  38. data/test/test_models.rb +212 -0
  39. data/test/test_module.rb +41 -71
  40. data/test/test_object.rb +1 -1
  41. data/test/test_objectstats.rb +1 -1
  42. data/test/test_pkgconfig.rb +7 -5
  43. data/test/test_proc.rb +1 -1
  44. data/test/test_set.rb +1 -1
  45. data/test/test_thread_pool.rb +409 -0
  46. data/test/test_time.rb +6 -6
  47. data/test/test_unbound_method.rb +1 -1
  48. metadata +157 -131
  49. data/README.txt +0 -45
  50. data/ext/extconf.rb +0 -29
  51. data/ext/utilrb_ext.cc +0 -144
@@ -1,200 +1,6 @@
1
- require 'utilrb/object/attribute'
2
- require 'utilrb/object/singleton_class'
3
- require 'utilrb/enumerable/uniq'
4
- require 'utilrb/module/include'
1
+ require 'utilrb/models/inherited_enumerable'
5
2
 
6
3
  class Module
7
- def define_inherited_enumerable(name, attribute_name = name, options = Hash.new, &init) # :nodoc:
8
- # Set up the attribute accessor
9
- attribute(attribute_name, &init)
10
- class_eval { private "#{attribute_name}=" }
11
-
12
- options[:enum_with] ||= :each
13
-
14
- class_eval <<-EOF, __FILE__, __LINE__+1
15
- def all_#{name}; each_#{name}.to_a end
16
- def self_#{name}; @#{attribute_name} end
17
- EOF
18
-
19
- if options[:map]
20
- class_eval <<-EOF, __FILE__, __LINE__+1
21
- def each_#{name}(key = nil, uniq = true)
22
- if !block_given?
23
- return enum_for(:each_#{name}, key, uniq)
24
- end
25
-
26
- if key
27
- for klass in ancestors
28
- if klass.instance_variable_defined?(:@#{attribute_name})
29
- if klass.#{attribute_name}.has_key?(key)
30
- yield(klass.#{attribute_name}[key])
31
- return self if uniq
32
- end
33
- end
34
- end
35
- elsif !uniq
36
- for klass in ancestors
37
- if klass.instance_variable_defined?(:@#{attribute_name})
38
- klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
39
- end
40
- end
41
- else
42
- seen = Set.new
43
- for klass in ancestors
44
- if klass.instance_variable_defined?(:@#{attribute_name})
45
- klass.#{attribute_name}.#{options[:enum_with]} do |el|
46
- unless seen.include?(el.first)
47
- seen << el.first
48
- yield(el)
49
- end
50
- end
51
- end
52
- end
53
-
54
- end
55
- self
56
- end
57
- def find_#{name}(key)
58
- each_#{name}(key, true) do |value|
59
- return value
60
- end
61
- nil
62
- end
63
- def has_#{name}?(key)
64
- for klass in ancestors
65
- if klass.instance_variable_defined?(:@#{attribute_name})
66
- return true if klass.#{attribute_name}.has_key?(key)
67
- end
68
- end
69
- false
70
- end
71
- EOF
72
- else
73
- class_eval <<-EOF, __FILE__, __LINE__+1
74
- def each_#{name}
75
- if !block_given?
76
- return enum_for(:each_#{name})
77
- end
78
-
79
- for klass in ancestors
80
- if klass.instance_variable_defined?(:@#{attribute_name})
81
- klass.#{attribute_name}.#{options[:enum_with]} { |el| yield(el) }
82
- end
83
- end
84
- self
85
- end
86
- EOF
87
- end
88
- end
89
-
90
- # Defines an attribute as being enumerable in the class instance and in the
91
- # whole class inheritance hierarchy. More specifically, it defines a
92
- # <tt>each_#{name}(&iterator)</tt> instance method and a <tt>each_#{name}(&iterator)</tt>
93
- # class method which iterates (in order) on
94
- # - the instance #{name} attribute
95
- # - the singleton class #{name} attribute
96
- # - the class #{name} attribute
97
- # - the superclass #{name} attribute
98
- # - the superclass' superclass #{name} attribute
99
- # ...
100
- #
101
- # This method can be used on modules, in which case the module is used as if
102
- # it was part of the inheritance hierarchy.
103
- #
104
- # The +name+ option defines the enumeration method name (+value+ will
105
- # define a +each_value+ method). +attribute_name+ defines the attribute
106
- # name. +init+ is a block called to initialize the attribute.
107
- # Valid options in +options+ are:
108
- # map::
109
- # If true, the attribute should respond to +[]+. In that case, the
110
- # enumeration method is each_value(key = nil, uniq = false) If +key+ is
111
- # given, we iterate on the values given by <tt>attribute[key]</tt>. If
112
- # +uniq+ is true, the enumeration will yield at most one value for each
113
- # +key+ found (so, if both +key+ and +uniq+ are given, the enumeration
114
- # yields at most one value). See the examples below
115
- # enum_with:: the enumeration method of the enumerable, if it is not +each+
116
- #
117
- # === Example
118
- # Let's define some classes and look at the ancestor chain
119
- #
120
- # class A; end
121
- # module M; end
122
- # class B < A; include M end
123
- # A.ancestors # => [A, Object, Kernel]
124
- # B.ancestors # => [B, M, A, Object, Kernel]
125
- #
126
- # ==== Attributes for which 'map' is not set
127
- #
128
- # class A
129
- # inherited_enumerable("value", "values") do
130
- # Array.new
131
- # end
132
- # end
133
- # module M
134
- # inherited_enumerable("mod") do
135
- # Array.new
136
- # end
137
- # end
138
- #
139
- # A.values << 1 # => [1]
140
- # B.values << 2 # => [2]
141
- # M.mod << 1 # => [1]
142
- # b = B.new
143
- # class << b
144
- # self.values << 3 # => [3]
145
- # self.mod << 4 # => [4]
146
- # end
147
- # M.mod << 2 # => [1, 2]
148
- #
149
- # A.enum_for(:each_value).to_a # => [1]
150
- # B.enum_for(:each_value).to_a # => [2, 1]
151
- # b.singleton_class.enum_for(:each_value).to_a # => [3, 2, 1]
152
- # b.singleton_class.enum_for(:each_mod).to_a # => [4, 1, 2]
153
- #
154
- # ==== Attributes for which 'map' is set
155
- #
156
- # class A
157
- # inherited_enumerable("mapped", "map", :map => true) do
158
- # Hash.new { |h, k| h[k] = Array.new }
159
- # end
160
- # end
161
- #
162
- # A.map['name'] = 'A' # => "A"
163
- # A.map['universe'] = 42
164
- # B.map['name'] = 'B' # => "B"
165
- # B.map['half_of_it'] = 21
166
- #
167
- # Let's see what happens if we don't specify the key option.
168
- # A.enum_for(:each_mapped).to_a # => [["name", "A"], ["universe", 42]]
169
- # If the +uniq+ option is set (the default), we see only B's value for 'name'
170
- # B.enum_for(:each_mapped).to_a # => [["half_of_it", 21], ["name", "B"], ["universe", 42]]
171
- # If the +uniq+ option is not set, we see both values for 'name'. Note that
172
- # since 'map' is a Hash, the order of keys in one class is not guaranteed.
173
- # Nonetheless, we have the guarantee that values from B appear before
174
- # those from A
175
- # B.enum_for(:each_mapped, nil, false).to_a # => [["half_of_it", 21], ["name", "B"], ["name", "A"], ["universe", 42]]
176
- #
177
- #
178
- # Now, let's see how 'key' behaves
179
- # A.enum_for(:each_mapped, 'name').to_a # => ["A"]
180
- # B.enum_for(:each_mapped, 'name').to_a # => ["B"]
181
- # B.enum_for(:each_mapped, 'name', false).to_a # => ["B", "A"]
182
- #
183
- def inherited_enumerable(name, attribute_name = name, options = Hash.new, &init)
184
- singleton_class.class_eval { define_inherited_enumerable(name, attribute_name, options, &init) }
185
-
186
- if is_a?(Module) && !is_a?(Class)
187
- unless const_defined?(:ClassExtension)
188
- const_set(:ClassExtension, Module.new)
189
- end
190
- class_extension = const_get(:ClassExtension)
191
- class_extension.class_eval do
192
- define_inherited_enumerable(name, attribute_name, options, &init)
193
- end
194
- end
195
- end
4
+ include Utilrb::Models
196
5
  end
197
6
 
198
-
199
-
200
-
@@ -19,12 +19,15 @@ module ObjectStats
19
19
  # Returns a klass => count hash counting the currently allocated objects
20
20
  #
21
21
  # It allocates 1 Hash, which is included in the count
22
- def self.count_by_class
22
+ def self.count_by_class(threshold = nil)
23
23
  by_class = Hash.new(0)
24
24
  ObjectSpace.each_object { |obj|
25
25
  by_class[obj.class] += 1
26
26
  by_class
27
27
  }
28
+ if threshold
29
+ by_class.delete_if { |kl, count| count < threshold }
30
+ end
28
31
 
29
32
  by_class
30
33
  end
@@ -45,7 +45,7 @@ module Utilrb
45
45
  attr_reader :loaded_packages
46
46
 
47
47
  def clear_cache
48
- cache.clear
48
+ loaded_packages.clear
49
49
  end
50
50
  end
51
51
  @loaded_packages = Hash.new
@@ -76,6 +76,14 @@ module Utilrb
76
76
  find_matching_version(candidates, version_spec)
77
77
  end
78
78
 
79
+ # Finds the provided package and optional version and returns its
80
+ # PkgConfig description
81
+ #
82
+ # @param [String] version_spec version specification, of the form "op
83
+ # number", where op is < <= >= > or == and the version number X, X.y,
84
+ # ...
85
+ # @return [PkgConfig] the pkg-config description
86
+ # @raise [NotFound] if the package is not found
79
87
  def self.new(name, version_spec = nil)
80
88
  get(name, version_spec)
81
89
  end
@@ -325,7 +333,7 @@ module Utilrb
325
333
  def include_dirs
326
334
  result = Shellwords.shellsplit(cflags_only_I).map { |v| v[2..-1] }
327
335
  if result.any?(&:empty?)
328
- raise Invalid, "empty include directory (-I without argument) found in pkg-config package #{name}"
336
+ raise Invalid.new(name), "empty include directory (-I without argument) found in pkg-config package #{name}"
329
337
  end
330
338
  result
331
339
  end
@@ -335,7 +343,7 @@ module Utilrb
335
343
  def library_dirs
336
344
  result = Shellwords.shellsplit(libs_only_L).map { |v| v[2..-1] }
337
345
  if result.any?(&:empty?)
338
- raise Invalid, "empty link directory (-L without argument) found in pkg-config package #{name}"
346
+ raise Invalid.new(name), "empty link directory (-L without argument) found in pkg-config package #{name}"
339
347
  end
340
348
  result
341
349
  end
@@ -396,9 +404,7 @@ module Utilrb
396
404
  if path = ENV['PKG_CONFIG_PATH']
397
405
  path.split(':').each(&block)
398
406
  end
399
- yield('/usr/local/lib/pkgconfig')
400
- yield('/usr/lib/pkgconfig')
401
- yield('/usr/share/pkgconfig')
407
+ default_search_path.each(&block)
402
408
  end
403
409
 
404
410
  # Returns true if there is a package with this name
@@ -443,6 +449,36 @@ module Utilrb
443
449
  end
444
450
  end
445
451
  end
452
+
453
+
454
+ FOUND_PATH_RX = /Scanning directory '(.*\/)((?:lib|share)\/.*)'$/
455
+ NONEXISTENT_PATH_RX = /Cannot open directory '.*\/((?:lib|share)\/.*)' in package search path:.*/
456
+
457
+ # Returns the system-wide search path that is embedded in pkg-config
458
+ def self.default_search_path
459
+ if !@default_search_path
460
+ output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n")
461
+ @default_search_path = output.grep(FOUND_PATH_RX).
462
+ map { |l| l.gsub(FOUND_PATH_RX, '\1\2') }
463
+ end
464
+ return @default_search_path
465
+ end
466
+
467
+ # Returns the system-wide standard suffixes that should be appended to
468
+ # new prefixes to find pkg-config files
469
+ def self.default_search_suffixes
470
+ if !@default_search_suffixes
471
+ output = `LANG=C PKG_CONFIG_PATH= pkg-config --debug 2>&1`.split("\n")
472
+ found_paths = output.grep(FOUND_PATH_RX).
473
+ map { |l| l.gsub(FOUND_PATH_RX, '\2') }.
474
+ to_set
475
+ not_found = output.grep(NONEXISTENT_PATH_RX).
476
+ map { |l| l.gsub(NONEXISTENT_PATH_RX, '\1') }.
477
+ to_set
478
+ @default_search_suffixes = found_paths | not_found
479
+ end
480
+ return @default_search_suffixes
481
+ end
446
482
  end
447
483
  end
448
484
 
@@ -7,9 +7,15 @@ module Utilrb
7
7
  rescue LoadError => e
8
8
  STDERR.puts "INFO: cannot load the Hoe gem. Distribution is disabled"
9
9
  STDERR.puts "INFO: error message is: #{e.message}"
10
+ if ::Rake.application.options.trace
11
+ puts e.backtrace.join("\n ")
12
+ end
10
13
  rescue Exception => e
11
14
  STDERR.puts "INFO: cannot load the Hoe gem, or Hoe fails. Distribution is disabled"
12
15
  STDERR.puts "INFO: error message is: #{e.message}"
16
+ if ::Rake.application.options.trace
17
+ puts e.backtrace.join("\n ")
18
+ end
13
19
  end
14
20
 
15
21
  def self.rdoc
@@ -19,9 +25,15 @@ module Utilrb
19
25
  rescue LoadError => e
20
26
  STDERR.puts "INFO: cannot load RDoc, Documentation generation is disabled"
21
27
  STDERR.puts "INFO: error message is: #{e.message}"
28
+ if ::Rake.application.options.trace
29
+ puts e.backtrace.join("\n ")
30
+ end
22
31
  rescue Exception => e
23
32
  STDERR.puts "INFO: cannot load the RDoc gem, or RDoc failed to load. Documentation generation is disabled"
24
33
  STDERR.puts "INFO: error message is: #{e.message}"
34
+ if ::Rake.application.options.trace
35
+ puts e.backtrace.join("\n ")
36
+ end
25
37
  end
26
38
  end
27
39
  end
@@ -1,3 +1,6 @@
1
+ require 'stringio'
2
+ require 'utilrb/value_set'
3
+ require 'utilrb/kernel/options'
1
4
  module Utilrb
2
5
  begin
3
6
  require 'roby/graph'
@@ -8,22 +11,46 @@ module Utilrb
8
11
 
9
12
  if has_roby_graph
10
13
  class RubyObjectGraph
14
+ module GraphGenerationObjectMaker
15
+ def __ruby_object_graph_internal__; end
16
+ end
11
17
  attr_reader :graph
12
18
  attr_reader :references
13
19
 
20
+ # Takes a snapshot of all objects currently live
14
21
  def self.snapshot
15
22
  # Doing any Ruby code that do not modify the heap is impossible. List
16
23
  # all existing objects once, and only then do the processing.
17
24
  # +live_objects+ will be referenced in itself, but we are going to
18
25
  # remove it later
19
26
  GC.disable
20
- live_objects = []
27
+ live_objects = Array.new
21
28
  ObjectSpace.each_object do |obj|
22
- if obj != live_objects
29
+ if obj.object_id != live_objects.object_id
23
30
  live_objects << obj
24
31
  end
25
32
  end
26
33
  GC.enable
34
+ live_objects
35
+ end
36
+
37
+ # Generates a graph of all the objects currently live, in dot format
38
+ def self.dot_snapshot(options = Hash.new)
39
+ r, w = IO.pipe
40
+ live_objects = RubyObjectGraph.snapshot
41
+ fork do
42
+ options, register_options = Kernel.filter_options options,
43
+ :collapse => nil
44
+ ruby_graph = RubyObjectGraph.new
45
+ ruby_graph.register_references_to(live_objects, register_options)
46
+ if options[:collapse]
47
+ ruby_graph.collapse(*options[:collapse])
48
+ end
49
+ w.write ruby_graph.to_dot
50
+ exit! true
51
+ end
52
+ w.close
53
+ r.read
27
54
  end
28
55
 
29
56
  def initialize
@@ -31,8 +58,22 @@ module Utilrb
31
58
  @references = Hash.new
32
59
  end
33
60
 
61
+ def clear
62
+ graph.each_edge do |from, to, info|
63
+ info.clear
64
+ end
65
+ graph.clear
66
+ references.clear
67
+ end
68
+
69
+ # This class is used to store any ruby object into a BGL::Graph (i.e.
70
+ # the live graph)
34
71
  class ObjectRef
72
+ # The referenced object
35
73
  attr_reader :obj
74
+
75
+ def __ruby_object_graph_internal__; end
76
+
36
77
  def initialize(obj)
37
78
  @obj = obj
38
79
  end
@@ -46,34 +87,63 @@ module Utilrb
46
87
  end
47
88
  end
48
89
 
90
+ # Register a ruby object reference
91
+ #
92
+ # @param [ObjectRef] obj_ref the object that is referencing
93
+ # @param [ObjectRef] var_ref the object that is referenced
94
+ # @param desc the description for the link. For instance, if obj_ref
95
+ # references var_ref because of an instance variable, this is going to
96
+ # be the name of the instance variable
97
+ # @return [void]
49
98
  def add_reference(obj_ref, var_ref, desc)
50
99
  if graph.linked?(obj_ref, var_ref)
51
- obj_ref[var_ref, graph] << desc
100
+ desc_set = obj_ref[var_ref, graph]
101
+ if !desc_set.include?(desc)
102
+ desc_set << desc
103
+ end
52
104
  else
53
- graph.link(obj_ref, var_ref, [desc].to_set)
105
+ desc_set = [desc]
106
+ graph.link(obj_ref, var_ref, desc_set)
54
107
  end
55
108
  end
56
109
 
57
- def register_references_to(klass, orig_options = Hash.new)
58
- options = Kernel.validate_options orig_options,
59
- :roots => nil
60
- roots = options[:roots]
61
-
62
- live_objects = RubyObjectGraph.snapshot
110
+ # Creates a BGL::Graph of ObjectRef objects which stores the current
111
+ # ruby object graph
112
+ #
113
+ # @param [Class] klass seed
114
+ # @option options [Array<Object>] roots (nil) if given, the list of root
115
+ # objects. Objects that are not referenced by one of these roots will
116
+ # not be included in the final graph
117
+ def register_references_to(live_objects, options = Hash.new)
118
+ orig_options = options # to exclude it from the graph
119
+ options = Kernel.validate_options options,
120
+ :roots => [Object], :excluded_classes => [], :excluded_objects => [],
121
+ :include_class_relation => false
122
+ roots_class, roots = options[:roots].partition { |obj| obj.kind_of?(Class) }
123
+ excluded_classes = options[:excluded_classes]
124
+ excluded_objects = options[:excluded_objects]
125
+ include_class_relation = options[:include_class_relation]
63
126
 
64
127
  # Create a single ObjectRef per (interesting) live object, so that we
65
128
  # can use a BGL::Graph to represent the reference graph. This will be
66
129
  # what we are going to access later on. Use object IDs since we really
67
130
  # want to refer to objects and not use eql? comparisons
68
- desired_seeds = []
69
- excludes = [live_objects, self, orig_options, options, roots].to_value_set
131
+ desired_seeds = roots.map(&:object_id)
132
+ excludes = [live_objects, self, graph, references, orig_options, options, roots, roots_class].to_value_set
133
+ live_objects_total = live_objects.size
70
134
  live_objects.delete_if do |obj|
71
- if excludes.include?(obj)
135
+ if obj.kind_of?(DRbObject)
136
+ true
137
+ elsif excludes.include?(obj) || obj.respond_to?(:__ruby_object_graph_internal__)
72
138
  true
73
139
  else
74
140
  references[obj.object_id] ||= ObjectRef.new(obj)
75
- if obj.kind_of?(klass)
76
- desired_seeds << obj.object_id
141
+ if roots_class.any? { |k| obj.kind_of?(k) }
142
+ if !excluded_classes.any? { |k| obj.kind_of?(k) }
143
+ if !excluded_objects.include?(obj)
144
+ desired_seeds << obj.object_id
145
+ end
146
+ end
77
147
  end
78
148
  false
79
149
  end
@@ -84,12 +154,24 @@ module Utilrb
84
154
  end
85
155
  ignored_enumeration = Hash.new
86
156
 
157
+ names = Hash[
158
+ :array => "Array",
159
+ :value_set => "ValueSet[]",
160
+ :vertex => "Vertex[]",
161
+ :edge => "Edge[]",
162
+ :hash_key => "Hash[key]",
163
+ :hash_value => "Hash[value]",
164
+ :proc => "Proc",
165
+ :ancestor => "Ancestor"]
166
+ puts "RubyObjectGraph: #{live_objects.size} objects found, #{desired_seeds.size} seeds and #{live_objects_total} total live objects"
87
167
  loop do
88
168
  old_graph_size = graph.size
89
169
  live_objects.each do |obj|
90
170
  obj_ref = references[obj.object_id]
91
171
 
92
- test_and_add_reference(obj_ref, obj.class, "class")
172
+ if include_class_relation
173
+ test_and_add_reference(obj_ref, obj.class, "class")
174
+ end
93
175
 
94
176
  for var_name in obj.instance_variables
95
177
  var = obj.instance_variable_get(var_name)
@@ -97,32 +179,39 @@ module Utilrb
97
179
  end
98
180
 
99
181
  case obj
100
- when Array, ValueSet
182
+ when Array
101
183
  for var in obj
102
- test_and_add_reference(obj_ref, var, "ValueSet[]")
184
+ test_and_add_reference(obj_ref, var, names[:array])
185
+ end
186
+ when ValueSet
187
+ for var in obj
188
+ test_and_add_reference(obj_ref, var, names[:value_set])
103
189
  end
104
190
  when BGL::Graph
105
191
  obj.each_vertex do
106
- test_and_add_reference(obj_ref, var, "Vertex[]")
192
+ test_and_add_reference(obj_ref, var, names[:vertex])
107
193
  end
108
194
  obj.each_edge do |_, _, info|
109
- test_and_add_reference(obj_ref, info, "Edge[]")
195
+ test_and_add_reference(obj_ref, info, names[:edge])
110
196
  end
111
197
  when Hash
112
198
  for var in obj
113
- 2.times do |i|
114
- test_and_add_reference(obj_ref, var[i], "Hash[]")
115
- end
199
+ test_and_add_reference(obj_ref, var[0], names[:hash_key])
200
+ test_and_add_reference(obj_ref, var[1], names[:hash_value])
116
201
  end
117
202
  when Proc
118
203
  if obj.respond_to?(:references)
119
204
  for var in obj.references
120
205
  begin
121
- test_and_add_reference(obj_ref, ObjectSpace._id2ref(var), "Proc")
206
+ test_and_add_reference(obj_ref, ObjectSpace._id2ref(var), names[:proc])
122
207
  rescue RangeError
123
208
  end
124
209
  end
125
210
  end
211
+ when Class
212
+ for ref in obj.ancestors
213
+ test_and_add_reference(obj_ref, ref, names[:ancestor])
214
+ end
126
215
  else
127
216
  if obj.respond_to?(:each)
128
217
  if obj.kind_of?(Module) || obj.kind_of?(Class)
@@ -133,7 +222,7 @@ module Utilrb
133
222
  else
134
223
  if !ignored_enumeration[obj.class]
135
224
  ignored_enumeration[obj.class] = true
136
- puts "ignoring enumerator object #{obj.class}"
225
+ puts "ignoring enumerator object of class #{obj.class}"
137
226
  end
138
227
  end
139
228
  end
@@ -143,6 +232,7 @@ module Utilrb
143
232
  break
144
233
  end
145
234
  end
235
+ live_objects.clear # to avoid making it a central node in future calls
146
236
  return graph
147
237
  end
148
238
 
@@ -169,63 +259,122 @@ module Utilrb
169
259
  end
170
260
  end
171
261
 
172
- def to_dot
173
- if !roots
174
- roots = graph.vertices.find_all do |v|
175
- v.root?(graph)
176
- end.to_value_set
177
- else
178
- roots = roots.to_value_set
262
+ def live_chain_for(obj)
263
+ result = []
264
+
265
+ # Find one Roby task and look at the chain
266
+ ref = references[obj.object_id]
267
+ stack = [[ref.obj]]
268
+ graph.reverse.each_dfs(ref, BGL::Graph::TREE) do |source, target, info, kind|
269
+ source = source.obj
270
+ target = target.obj
271
+ if stack.last.first.object_id != source.object_id && stack.any? { |obj, _| obj.object_id == source.object_id }
272
+ result << stack.dup if stack.size != 1
273
+ while stack.last.first.object_id != source.object_id
274
+ stack.pop
275
+ end
276
+ end
277
+ stack.push [target, info]
179
278
  end
279
+ if stack.size != 1
280
+ result << stack
281
+ end
282
+ result
283
+ end
284
+
285
+
286
+ def display_live_chains(chains)
287
+ chains.each do |stack|
288
+ puts stack.map { |obj, link| "#{link} #{obj}" }.join("\n >")
289
+ end
290
+ end
291
+
292
+ def to_dot
293
+ roots = graph.vertices.find_all do |v|
294
+ v.root?(graph)
295
+ end.to_value_set
180
296
 
181
297
  io = StringIO.new("")
182
298
 
299
+ colors = Hash[
300
+ :green => 'green',
301
+ :magenta => 'magenta',
302
+ :black => 'black'
303
+ ]
304
+ obj_label_format = "obj%i [label=\"%s\",color=%s];"
305
+ obj_label_format_elements = []
306
+ edge_label_format_0 = "obj%i -> obj%i [label=\""
307
+ edge_label_format_1 = "\"];"
308
+ edge_label_format_elements = []
309
+
183
310
  all_seen = ValueSet.new
311
+ seen = ValueSet.new
184
312
  io.puts "digraph {"
185
313
  roots.each do |obj_ref|
186
- seen = ValueSet.new
187
314
  graph.each_dfs(obj_ref, BGL::Graph::ALL) do |from_ref, to_ref, all_info, kind|
188
- info = all_info.to_a.join(",")
315
+ info = []
316
+ for str in all_info
317
+ info << str
318
+ end
189
319
 
190
320
  if all_seen.include?(from_ref)
191
321
  graph.prune
192
322
  else
193
323
  from_id = from_ref.obj.object_id
194
324
  to_id = to_ref.obj.object_id
195
- io.puts "obj#{from_id} -> obj#{to_id} [label=\"#{info}\"]"
325
+
326
+ edge_label_format_elements.clear
327
+ edge_label_format_elements << from_id << to_id
328
+ str = edge_label_format_0 % edge_label_format_elements
329
+ first = true
330
+ for edge_info in all_info
331
+ if !first
332
+ str << ","
333
+ end
334
+ str << edge_info
335
+ first = false
336
+ end
337
+ str << edge_label_format_1
338
+
339
+ io.puts str
196
340
  end
197
341
  seen << from_ref << to_ref
198
342
  end
199
343
 
200
- seen.each do |obj_ref|
344
+ for obj_ref in seen
201
345
  if !all_seen.include?(obj_ref)
202
346
  obj = obj_ref.obj
203
347
  obj_id = obj_ref.obj.object_id
204
348
  str =
205
349
  if obj.respond_to?(:each)
206
- "#{obj.class}"
350
+ "#{obj.class}: #{obj.object_id}"
207
351
  else
208
352
  obj.to_s
209
353
  end
210
354
 
355
+ str = str.encode('UTF-8', :invalid => :replace, :undef => :replace, :replace => "")
356
+ str = str.gsub(/[^\w\.:<>]/, "")
357
+
211
358
  color =
212
- if obj.kind_of?(Roby::EventGenerator)
213
- "cyan"
214
- elsif obj.kind_of?(Roby::Task)
215
- "blue"
216
- elsif obj.kind_of?(BGL::Graph)
217
- "magenta"
359
+ if obj.kind_of?(BGL::Graph)
360
+ :magenta
218
361
  elsif obj.kind_of?(Hash) || obj.kind_of?(Array) || obj.kind_of?(ValueSet)
219
- "green"
362
+ :green
220
363
  else
221
- "black"
364
+ :black
222
365
  end
223
366
 
224
- io.puts "obj#{obj_id} [label=\"#{str}\",color=#{color}];"
367
+ obj_label_format_elements = [obj_id, str.gsub(/[\\"\n]/, " "), colors[color]]
368
+ str = obj_label_format % obj_label_format_elements
369
+ io.puts(str)
225
370
  end
226
371
  end
227
372
  all_seen.merge(seen)
373
+ seen.clear
228
374
  end
375
+ roots.clear
376
+ all_seen.clear
377
+ seen.clear
229
378
  io.puts "}"
230
379
  io.string
231
380
  end