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.
- data/Manifest.txt +10 -9
- data/README.rd +23 -0
- data/Rakefile +40 -37
- data/ext/utilrb/extconf.rb +36 -0
- data/ext/{proc.cc → utilrb/proc.c} +7 -6
- data/ext/utilrb/ruby_allocator.hh +76 -0
- data/ext/{ruby_internals-1.8.h → utilrb/ruby_internals-1.8.h} +0 -0
- data/ext/{ruby_internals-1.9.h → utilrb/ruby_internals-1.9.h} +0 -0
- data/ext/{swap.cc → utilrb/swap.cc} +0 -0
- data/ext/utilrb/utilrb.cc +79 -0
- data/ext/{value_set.cc → utilrb/value_set.cc} +5 -5
- data/ext/{weakref.cc → utilrb/weakref.cc} +0 -0
- data/lib/utilrb/common.rb +7 -2
- data/lib/utilrb/doc/rake.rb +46 -15
- data/lib/utilrb/kernel/load_dsl_file.rb +2 -2
- data/lib/utilrb/kernel/options.rb +6 -0
- data/lib/utilrb/logger/forward.rb +7 -0
- data/lib/utilrb/logger/hierarchy.rb +100 -42
- data/lib/utilrb/logger/indent.rb +38 -3
- data/lib/utilrb/logger/log_pp.rb +1 -1
- data/lib/utilrb/logger/root.rb +27 -12
- data/lib/utilrb/module/inherited_enumerable.rb +2 -196
- data/lib/utilrb/objectstats.rb +4 -1
- data/lib/utilrb/pkgconfig.rb +42 -6
- data/lib/utilrb/rake_common.rb +12 -0
- data/lib/utilrb/ruby_object_graph.rb +195 -46
- data/lib/utilrb/yard.rb +89 -89
- data/test/test_array.rb +1 -1
- data/test/test_dir.rb +1 -1
- data/test/test_enumerable.rb +7 -1
- data/test/test_event_loop.rb +407 -0
- data/test/test_exception.rb +1 -1
- data/test/test_gc.rb +1 -1
- data/test/test_hash.rb +57 -1
- data/test/test_kernel.rb +52 -21
- data/test/test_logger.rb +150 -1
- data/test/test_misc.rb +1 -1
- data/test/test_models.rb +212 -0
- data/test/test_module.rb +41 -71
- data/test/test_object.rb +1 -1
- data/test/test_objectstats.rb +1 -1
- data/test/test_pkgconfig.rb +7 -5
- data/test/test_proc.rb +1 -1
- data/test/test_set.rb +1 -1
- data/test/test_thread_pool.rb +409 -0
- data/test/test_time.rb +6 -6
- data/test/test_unbound_method.rb +1 -1
- metadata +157 -131
- data/README.txt +0 -45
- data/ext/extconf.rb +0 -29
- data/ext/utilrb_ext.cc +0 -144
@@ -1,200 +1,6 @@
|
|
1
|
-
require 'utilrb/
|
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
|
-
|
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
|
-
|
data/lib/utilrb/objectstats.rb
CHANGED
@@ -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
|
data/lib/utilrb/pkgconfig.rb
CHANGED
@@ -45,7 +45,7 @@ module Utilrb
|
|
45
45
|
attr_reader :loaded_packages
|
46
46
|
|
47
47
|
def clear_cache
|
48
|
-
|
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
|
-
|
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
|
|
data/lib/utilrb/rake_common.rb
CHANGED
@@ -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]
|
100
|
+
desc_set = obj_ref[var_ref, graph]
|
101
|
+
if !desc_set.include?(desc)
|
102
|
+
desc_set << desc
|
103
|
+
end
|
52
104
|
else
|
53
|
-
|
105
|
+
desc_set = [desc]
|
106
|
+
graph.link(obj_ref, var_ref, desc_set)
|
54
107
|
end
|
55
108
|
end
|
56
109
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
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?(
|
76
|
-
|
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
|
-
|
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
|
182
|
+
when Array
|
101
183
|
for var in obj
|
102
|
-
test_and_add_reference(obj_ref, var,
|
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,
|
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,
|
195
|
+
test_and_add_reference(obj_ref, info, names[:edge])
|
110
196
|
end
|
111
197
|
when Hash
|
112
198
|
for var in obj
|
113
|
-
|
114
|
-
|
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),
|
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
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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?(
|
213
|
-
|
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
|
-
|
362
|
+
:green
|
220
363
|
else
|
221
|
-
|
364
|
+
:black
|
222
365
|
end
|
223
366
|
|
224
|
-
|
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
|