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