mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Parse ObjectSpace.dump_all / dump arguments in Ruby to avoid allocation noise
[Feature #17045] ObjectSpace.dump_all should allocate as little as possible in the GC heap Up until this commit ObjectSpace.dump_all allocates two Hash because of `rb_scan_args`. It also can allocate a `File` because of `rb_io_get_write_io`. These allocations are problematic because `dump_all` dumps the Ruby heap, so it should try modify as little as possible what it is observing.
This commit is contained in:
parent
a0d50465de
commit
fbba6bd4e3
Notes:
git
2020-09-16 01:18:39 +09:00
2 changed files with 115 additions and 118 deletions
87
ext/objspace/lib/objspace.rb
Normal file
87
ext/objspace/lib/objspace.rb
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'objspace.so'
|
||||||
|
|
||||||
|
module ObjectSpace
|
||||||
|
class << self
|
||||||
|
private :_dump
|
||||||
|
private :_dump_all
|
||||||
|
end
|
||||||
|
|
||||||
|
module_function
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
|
||||||
|
# ObjectSpace.dump(obj, output: :file) # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json>
|
||||||
|
# ObjectSpace.dump(obj, output: :stdout) # => nil
|
||||||
|
#
|
||||||
|
# Dump the contents of a ruby object as JSON.
|
||||||
|
#
|
||||||
|
# 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(obj, output: :string)
|
||||||
|
out = case output
|
||||||
|
when :file, nil
|
||||||
|
require 'tempfile'
|
||||||
|
Tempfile.create(%w(rubyobj .json))
|
||||||
|
when :stdout
|
||||||
|
STDOUT
|
||||||
|
when :string
|
||||||
|
+''
|
||||||
|
when IO
|
||||||
|
output
|
||||||
|
else
|
||||||
|
raise ArgumentError, "wrong output option: #{output.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
_dump(obj, out)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# call-seq:
|
||||||
|
# ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json>
|
||||||
|
# ObjectSpace.dump_all(output: :stdout) # => nil
|
||||||
|
# ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."
|
||||||
|
# ObjectSpace.dump_all(output:
|
||||||
|
# File.open('heap.json','w')) # => #<File:heap.json>
|
||||||
|
# ObjectSpace.dump_all(output: :string,
|
||||||
|
# since: 42) # => "{...}\n{...}\n..."
|
||||||
|
#
|
||||||
|
# Dump the contents of the ruby heap as JSON.
|
||||||
|
#
|
||||||
|
# _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
|
||||||
|
# are ignored. See ::trace_object_allocations for more information and
|
||||||
|
# examples.
|
||||||
|
#
|
||||||
|
# If _since_ is omitted or is +nil+, all objects are dumped.
|
||||||
|
#
|
||||||
|
# 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)
|
||||||
|
out = case output
|
||||||
|
when :file, nil
|
||||||
|
require 'tempfile'
|
||||||
|
Tempfile.create(%w(rubyheap .json))
|
||||||
|
when :stdout
|
||||||
|
STDOUT
|
||||||
|
when :string
|
||||||
|
+''
|
||||||
|
when IO
|
||||||
|
output
|
||||||
|
else
|
||||||
|
raise ArgumentError, "wrong output option: #{output.inspect}"
|
||||||
|
end
|
||||||
|
|
||||||
|
_dump_all(out, full, since)
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,9 +25,6 @@
|
||||||
|
|
||||||
RUBY_EXTERN const char ruby_hexdigits[];
|
RUBY_EXTERN const char ruby_hexdigits[];
|
||||||
|
|
||||||
static VALUE sym_output, sym_stdout, sym_string, sym_file;
|
|
||||||
static VALUE sym_full, sym_since;
|
|
||||||
|
|
||||||
#define BUFFER_CAPACITY 4096
|
#define BUFFER_CAPACITY 4096
|
||||||
|
|
||||||
struct dump_config {
|
struct dump_config {
|
||||||
|
@ -539,142 +536,62 @@ root_obj_i(const char *category, VALUE obj, void *data)
|
||||||
dc->roots = 1;
|
dc->roots = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static void
|
||||||
dump_output(struct dump_config *dc, VALUE opts, VALUE output, const char *filename)
|
dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since)
|
||||||
{
|
{
|
||||||
VALUE tmp;
|
|
||||||
|
|
||||||
dc->full_heap = 0;
|
dc->full_heap = 0;
|
||||||
dc->buffer_len = 0;
|
dc->buffer_len = 0;
|
||||||
|
|
||||||
if (RTEST(opts)) {
|
if (TYPE(output) == T_STRING) {
|
||||||
output = rb_hash_aref(opts, sym_output);
|
dc->stream = Qfalse;
|
||||||
|
dc->string = output;
|
||||||
if (Qtrue == rb_hash_lookup2(opts, sym_full, Qfalse))
|
} else {
|
||||||
dc->full_heap = 1;
|
dc->stream = output;
|
||||||
|
dc->string = Qfalse;
|
||||||
VALUE since = rb_hash_aref(opts, sym_since);
|
|
||||||
if (RTEST(since)) {
|
|
||||||
dc->partial_dump = 1;
|
|
||||||
dc->since = NUM2SIZET(since);
|
|
||||||
} else {
|
|
||||||
dc->partial_dump = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output == sym_stdout) {
|
if (full == Qtrue) {
|
||||||
dc->stream = rb_stdout;
|
dc->full_heap = 1;
|
||||||
dc->string = Qnil;
|
|
||||||
}
|
|
||||||
else if (output == sym_file || output == Qnil) {
|
|
||||||
rb_require("tempfile");
|
|
||||||
tmp = rb_assoc_new(rb_str_new_cstr(filename), rb_str_new_cstr(".json"));
|
|
||||||
tmp = rb_funcallv(rb_path2class("Tempfile"), rb_intern("create"), 1, &tmp);
|
|
||||||
io:
|
|
||||||
dc->string = Qnil;
|
|
||||||
dc->stream = rb_io_get_write_io(tmp);
|
|
||||||
}
|
|
||||||
else if (output == sym_string) {
|
|
||||||
dc->string = rb_str_new_cstr("");
|
|
||||||
}
|
|
||||||
else if (!NIL_P(tmp = rb_io_check_io(output))) {
|
|
||||||
output = sym_file;
|
|
||||||
goto io;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
rb_raise(rb_eArgError, "wrong output option: %"PRIsVALUE, output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
if (RTEST(since)) {
|
||||||
|
dc->partial_dump = 1;
|
||||||
|
dc->since = NUM2SIZET(since);
|
||||||
|
} else {
|
||||||
|
dc->partial_dump = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
dump_result(struct dump_config *dc, VALUE output)
|
dump_result(struct dump_config *dc)
|
||||||
{
|
{
|
||||||
dump_flush(dc);
|
dump_flush(dc);
|
||||||
|
|
||||||
if (output == sym_string) {
|
if (dc->string) {
|
||||||
return rb_str_resurrect(dc->string);
|
return dc->string;
|
||||||
}
|
} else {
|
||||||
else if (output == sym_file) {
|
|
||||||
rb_io_flush(dc->stream);
|
rb_io_flush(dc->stream);
|
||||||
return dc->stream;
|
return dc->stream;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return Qnil;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* call-seq:
|
|
||||||
* ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
|
|
||||||
* ObjectSpace.dump(obj, output: :file) # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json>
|
|
||||||
* ObjectSpace.dump(obj, output: :stdout) # => nil
|
|
||||||
*
|
|
||||||
* Dump the contents of a ruby object as JSON.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
objspace_dump(int argc, VALUE *argv, VALUE os)
|
objspace_dump(VALUE os, VALUE obj, VALUE output)
|
||||||
{
|
{
|
||||||
static const char filename[] = "rubyobj";
|
|
||||||
VALUE obj = Qnil, opts = Qnil, output;
|
|
||||||
struct dump_config dc = {0,};
|
struct dump_config dc = {0,};
|
||||||
|
dump_output(&dc, output, Qnil, Qnil);
|
||||||
rb_scan_args(argc, argv, "1:", &obj, &opts);
|
|
||||||
|
|
||||||
output = dump_output(&dc, opts, sym_string, filename);
|
|
||||||
|
|
||||||
dump_object(obj, &dc);
|
dump_object(obj, &dc);
|
||||||
|
|
||||||
return dump_result(&dc, output);
|
return dump_result(&dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* call-seq:
|
|
||||||
* ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json>
|
|
||||||
* ObjectSpace.dump_all(output: :stdout) # => nil
|
|
||||||
* ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."
|
|
||||||
* ObjectSpace.dump_all(output:
|
|
||||||
* File.open('heap.json','w')) # => #<File:heap.json>
|
|
||||||
* ObjectSpace.dump_all(output: :string,
|
|
||||||
* since: 42) # => "{...}\n{...}\n..."
|
|
||||||
*
|
|
||||||
* Dump the contents of the ruby heap as JSON.
|
|
||||||
*
|
|
||||||
* _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
|
|
||||||
* are ignored. See ::trace_object_allocations for more information and
|
|
||||||
* examples.
|
|
||||||
*
|
|
||||||
* If _since_ is omitted or is +nil+, all objects are dumped.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static VALUE
|
static VALUE
|
||||||
objspace_dump_all(int argc, VALUE *argv, VALUE os)
|
objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since)
|
||||||
{
|
{
|
||||||
static const char filename[] = "rubyheap";
|
|
||||||
VALUE opts = Qnil, output;
|
|
||||||
struct dump_config dc = {0,};
|
struct dump_config dc = {0,};
|
||||||
|
dump_output(&dc, output, full, since);
|
||||||
rb_scan_args(argc, argv, "0:", &opts);
|
|
||||||
|
|
||||||
output = dump_output(&dc, opts, sym_file, filename);
|
|
||||||
|
|
||||||
if (!dc.partial_dump || dc.since == 0) {
|
if (!dc.partial_dump || dc.since == 0) {
|
||||||
/* dump roots */
|
/* dump roots */
|
||||||
|
@ -685,7 +602,7 @@ objspace_dump_all(int argc, VALUE *argv, VALUE os)
|
||||||
/* dump all objects */
|
/* dump all objects */
|
||||||
rb_objspace_each_objects(heap_i, &dc);
|
rb_objspace_each_objects(heap_i, &dc);
|
||||||
|
|
||||||
return dump_result(&dc, output);
|
return dump_result(&dc);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -696,15 +613,8 @@ Init_objspace_dump(VALUE rb_mObjSpace)
|
||||||
rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
|
rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
rb_define_module_function(rb_mObjSpace, "dump", objspace_dump, -1);
|
rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2);
|
||||||
rb_define_module_function(rb_mObjSpace, "dump_all", objspace_dump_all, -1);
|
rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 3);
|
||||||
|
|
||||||
sym_output = ID2SYM(rb_intern("output"));
|
|
||||||
sym_stdout = ID2SYM(rb_intern("stdout"));
|
|
||||||
sym_string = ID2SYM(rb_intern("string"));
|
|
||||||
sym_since = ID2SYM(rb_intern("since"));
|
|
||||||
sym_file = ID2SYM(rb_intern("file"));
|
|
||||||
sym_full = ID2SYM(rb_intern("full"));
|
|
||||||
|
|
||||||
/* force create static IDs */
|
/* force create static IDs */
|
||||||
rb_obj_gc_flags(rb_mObjSpace, 0, 0);
|
rb_obj_gc_flags(rb_mObjSpace, 0, 0);
|
||||||
|
|
Loading…
Reference in a new issue