summaryrefslogtreecommitdiff
path: root/ext/objspace/lib/objspace.rb
diff options
context:
space:
mode:
authorJean Boussier <[email protected]>2022-12-06 12:56:51 +0100
committerJean Boussier <[email protected]>2022-12-08 18:46:16 +0100
commit73771e4b192f3db62efb854affdfc95babba1d35 (patch)
tree6f15d7fea885b5e639c82b51d5c3fd50b9200735 /ext/objspace/lib/objspace.rb
parentb19490f75dd790f2f886df2c05ed8fba947326a9 (diff)
ObjectSpace.dump_all: dump shapes as well
I see several arguments in doing so. First they use a non trivial amount of memory, so for various memory profiling/mapping tools it is relevant to have visibility of the space occupied by shapes. Then, some pathological code can create a tons of shape, so it is valuable to have a way to have a way to observe shapes without having to compile Ruby with `SHAPE_DEBUG=1`. And additionally it's likely much faster to dump then this way than to use `RubyVM::Shape`. There are however a few open questions: - Shapes can't respect the `since:` argument. Not sure what to do when it is provided. Would probably make sense to not dump them. - Maybe it would make more sense to have a separate `ObjectSpace.dump_shapes`? - Maybe instead `dump_all` should take a `shapes: false` argument? Additionally, `ObjectSpace.dump_shapes` is added for the use case of debugging the evolution of the shape tree.
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/6868
Diffstat (limited to 'ext/objspace/lib/objspace.rb')
-rw-r--r--ext/objspace/lib/objspace.rb65
1 files changed, 60 insertions, 5 deletions
diff --git a/ext/objspace/lib/objspace.rb b/ext/objspace/lib/objspace.rb
index 0298b0646c..f8a66d8d32 100644
--- a/ext/objspace/lib/objspace.rb
+++ b/ext/objspace/lib/objspace.rb
@@ -6,6 +6,7 @@ module ObjectSpace
class << self
private :_dump
private :_dump_all
+ private :_dump_shapes
end
module_function
@@ -53,23 +54,38 @@ module ObjectSpace
#
# Dump the contents of the ruby heap as JSON.
#
+ #. _full__ must be a boolean. If true all heap slots are dumped including the empty ones (T_NONE).
+ #
# _since_ must be a non-negative integer or +nil+.
#
# If _since_ is a positive integer, only objects of that generation and
# newer generations are dumped. The current generation can be accessed using
- # GC::count.
- #
- # Objects that were allocated without object allocation tracing enabled
+ # GC::count. Objects that were allocated without object allocation tracing enabled
# are ignored. See ::trace_object_allocations for more information and
# examples.
#
# If _since_ is omitted or is +nil+, all objects are dumped.
#
+ # _shapes_ must be a boolean or a non-negative integer.
+ #
+ # If _shapes_ is a positive integer, only shapes newer than the provided
+ # shape id are dumped. The current shape_id can be accessed using +RubyVM.stat(:next_shape_id)+.
+ #
+ # If _shapes_ is +false+, no shapes are dumped.
+ #
+ # To only dump objects allocated past a certain point you can combine _since_ and _shapes_:
+ # ObjectSpace.trace_object_allocations
+ # GC.start
+ # gc_generation = GC.count
+ # shape_generation = RubyVM.stat(:next_shape_id)
+ #. call_method_to_instrument
+ # ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation)
+ #
# This method is only expected to work with C Ruby.
# This is an experimental method and is subject to change.
# In particular, the function signature and output format are
# not guaranteed to be compatible in future versions of ruby.
- def dump_all(output: :file, full: false, since: nil)
+ def dump_all(output: :file, full: false, since: nil, shapes: true)
out = case output
when :file, nil
require 'tempfile'
@@ -84,7 +100,46 @@ module ObjectSpace
raise ArgumentError, "wrong output option: #{output.inspect}"
end
- ret = _dump_all(out, full, since)
+ shapes = 0 if shapes == true
+ ret = _dump_all(out, full, since, shapes)
+ return nil if output == :stdout
+ ret
+ end
+
+ # call-seq:
+ # ObjectSpace.dump_shapes([output: :file]) # => #<File:/tmp/rubyshapes20131125-88469-laoj3v.json>
+ # ObjectSpace.dump_shapes(output: :stdout) # => nil
+ # ObjectSpace.dump_shapes(output: :string) # => "{...}\n{...}\n..."
+ # ObjectSpace.dump_shapes(output:
+ # File.open('shapes.json','w')) # => #<File:shapes.json>
+ # ObjectSpace.dump_all(output: :string,
+ # since: 42) # => "{...}\n{...}\n..."
+ #
+ # Dump the contents of the ruby shape tree as JSON.
+ #
+ # If _shapes_ is a positive integer, only shapes newer than the provided
+ # shape id are dumped. The current shape_id can be accessed using +RubyVM.stat(:next_shape_id)+.
+ #
+ # This method is only expected to work with C Ruby.
+ # This is an experimental method and is subject to change.
+ # In particular, the function signature and output format are
+ # not guaranteed to be compatible in future versions of ruby.
+ def dump_shapes(output: :file, since: 0)
+ out = case output
+ when :file, nil
+ require 'tempfile'
+ Tempfile.create(%w(rubyshapes .json))
+ when :stdout
+ STDOUT
+ when :string
+ +''
+ when IO
+ output
+ else
+ raise ArgumentError, "wrong output option: #{output.inspect}"
+ end
+
+ ret = _dump_shapes(out, since)
return nil if output == :stdout
ret
end