utilrb 1.6.6 → 2.0

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