mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 08:25:20 +08:00
Enable DTrace USDT GC probes in the modular default GC
When Ruby is built with `--with-modular-gc`, the default GC module is
built with `BUILDING_MODULAR_GC` defined.
The existing code stubbed out `probes.h` and `RUBY_DTRACE_GC_HOOK` in
this build path, so any DTrace script attached against the modular
default GC saw zero GC events.
This PR enables the GC probes in the modular GC library so they fire
when Ruby is built with `--enable-dtrace --with-modular-gc`
To test: build with `./configure --enable-dtrace
--with-modular-gc=/tmp/modgc`, then `make install-modular-gc
MODULAR_GC=default`.
Probes are present in the shared library:
```console
$ sudo bpftrace -l 'usdt:/tmp/modgc/librubygc.default.so:ruby:*'
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__begin
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__end
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__begin
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__end
```
They fire under a GC-heavy workload (`test.rb` contains `100000.times { Object.new }; GC.start`):
```console
$ sudo bpftrace -e '
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__begin { @mark_begin++ }
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__mark__end { @mark_end++ }
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__begin { @sweep_begin++ }
usdt:/tmp/modgc/librubygc.default.so:ruby:gc__sweep__end { @sweep_end++ }
' -c 'env RUBY_GC_LIBRARY=default ./miniruby --disable=gems test.rb'
@mark_begin: 2
@mark_end: 2
@sweep_begin: 18
@sweep_end: 18
```
This commit is contained in:
1 parent
8e8682fc23
commit
4fb0596b01
5 files changed
+88
-15
Notes:
git
2026-06-11 18:10:54 +00:00
No files matched your search
@@ -1975,7 +1975,9 @@ gc/distclean gc/realclean::
|
||||
|
||||
modular-gc-precheck:
|
||||
modular-gc: probes.h gc/Makefile
|
||||
$(Q) $(RUNRUBY) $(srcdir)/ext/extmk.rb \
|
||||
$(Q) DTRACE='$(DTRACE)' DTRACE_EXT='$(DTRACE_EXT)' \
|
||||
DTRACE_OBJ='$(DTRACE_OBJ)' DTRACE_REBUILD='$(DTRACE_REBUILD)' \
|
||||
$(RUNRUBY) $(srcdir)/ext/extmk.rb \
|
||||
$(SCRIPT_ARGS) \
|
||||
--make='$(MAKE)' --make-flags="V=$(V) MINIRUBY='$(MINIRUBY)'" \
|
||||
--gnumake=$(gnumake) --extflags="$(EXTLDFLAGS)" \
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
#include "gc/gc.h"
|
||||
#include "gc/gc_impl.h"
|
||||
|
||||
#ifndef BUILDING_MODULAR_GC
|
||||
# include "probes.h"
|
||||
#endif
|
||||
#include "probes.h"
|
||||
|
||||
#ifdef BUILDING_MODULAR_GC
|
||||
# define RB_DEBUG_COUNTER_INC(_name) ((void)0)
|
||||
@@ -8940,12 +8938,8 @@ gc_prof_timer_stop(rb_objspace_t *objspace)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef BUILDING_MODULAR_GC
|
||||
# define RUBY_DTRACE_GC_HOOK(name)
|
||||
#else
|
||||
# define RUBY_DTRACE_GC_HOOK(name) \
|
||||
#define RUBY_DTRACE_GC_HOOK(name) \
|
||||
do {if (RUBY_DTRACE_GC_##name##_ENABLED()) RUBY_DTRACE_GC_##name();} while (0)
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
gc_prof_mark_timer_start(rb_objspace_t *objspace)
|
||||
|
||||
+40
-2
@@ -4,11 +4,49 @@ require "mkmf"
|
||||
|
||||
srcdir = File.join(__dir__, "..")
|
||||
$INCFLAGS << " -I#{srcdir}"
|
||||
|
||||
$CPPFLAGS << " -DBUILDING_MODULAR_GC"
|
||||
|
||||
append_cflags("-fPIC")
|
||||
|
||||
DTRACE_RULES = <<~MAKE
|
||||
|
||||
probes.stamp: $(GC_DTRACE_OBJS)
|
||||
$(Q) if test -f $@ -o -f $(GC_DTRACE_OBJ); then \
|
||||
$(RM) $(GC_DTRACE_OBJS) $@; \
|
||||
$(ECHO) rebuilding objects which were modified by "dtrace -G"; \
|
||||
$(MAKE) $(GC_DTRACE_OBJS); \
|
||||
fi
|
||||
$(Q) touch $@
|
||||
|
||||
$(GC_DTRACE_OBJ): $(top_srcdir)/probes.d $(GC_DTRACE_REBUILD:yes=probes.stamp)
|
||||
$(ECHO) processing GC probes in object files
|
||||
$(Q) CC="$(CC)" CFLAGS="$(CFLAGS) $(INCFLAGS) $(CPPFLAGS)" $(GC_DTRACE) -G -C $(INCFLAGS) -s $(top_srcdir)/probes.d -o $@ $(GC_DTRACE_OBJS)
|
||||
MAKE
|
||||
|
||||
def create_gc_makefile(name, &block)
|
||||
create_makefile("librubygc.#{name}", &block)
|
||||
dtrace_obj = ENV.fetch("DTRACE_OBJ", "")
|
||||
dtrace_enabled = (name == "default" && !dtrace_obj.empty?)
|
||||
|
||||
if name == "default"
|
||||
$INCFLAGS << " -I$(topdir)"
|
||||
$headers << "$(topdir)/probes.h"
|
||||
$cleanfiles << "probes.stamp"
|
||||
end
|
||||
|
||||
create_makefile("librubygc.#{name}") do |conf|
|
||||
conf = block.call(conf) if block
|
||||
next conf unless dtrace_enabled
|
||||
|
||||
gc_objs = $objs.join(" ")
|
||||
conf = Array(conf).join.sub(/^OBJS = .*$/, "OBJS = #{gc_objs} #{dtrace_obj}")
|
||||
conf + <<~MAKE
|
||||
|
||||
GC_DTRACE = #{ENV.fetch("DTRACE")}
|
||||
GC_DTRACE_OBJ = #{dtrace_obj}
|
||||
GC_DTRACE_OBJS = #{gc_objs}
|
||||
GC_DTRACE_REBUILD = #{ENV.fetch("DTRACE_REBUILD", "")}
|
||||
MAKE
|
||||
end
|
||||
|
||||
File.write("Makefile", DTRACE_RULES, mode: "ab") if dtrace_enabled
|
||||
end
|
||||
@@ -115,7 +115,7 @@ module DTrace
|
||||
IO.popen(cmd, err: [:child, :out], &:readlines)
|
||||
end
|
||||
|
||||
def trap_probe d_program, ruby_program
|
||||
def trap_probe d_program, ruby_program, env: {}
|
||||
if Hash === d_program
|
||||
d_program = d_program[IMPL] or
|
||||
omit "#{d_program} not implemented for #{IMPL}"
|
||||
@@ -132,15 +132,17 @@ module DTrace
|
||||
|
||||
d_path = d.path
|
||||
rb_path = rb.path
|
||||
cmd = "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}"
|
||||
ruby_env = env
|
||||
env_prefix = ruby_env.map {|key, val| "#{key}=#{val}" }
|
||||
cmd = [*env_prefix, "#{RUBYBIN} --disable=gems -I#{INCLUDE} #{rb_path}"].join(" ")
|
||||
if IMPL == :stap
|
||||
cmd = %W(stap #{d_path} -c #{cmd})
|
||||
else
|
||||
cmd = [*DTRACE_CMD, "-q", "-s", d_path, "-c", cmd ]
|
||||
end
|
||||
if sudo = @@sudo
|
||||
NEEDED_ENVS.each do |name|
|
||||
if val = ENV[name]
|
||||
(NEEDED_ENVS + ruby_env.keys).uniq.each do |name|
|
||||
if val = ruby_env[name] || ENV[name]
|
||||
cmd.unshift("#{name}=#{val}")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: false
|
||||
require_relative 'helper'
|
||||
|
||||
module DTrace
|
||||
class TestModularGC < TestCase
|
||||
MODULAR_GC_DIR = RbConfig::CONFIG["modular_gc_dir"]
|
||||
DLEXT = RbConfig::CONFIG["DLEXT"]
|
||||
DEFAULT_GC = MODULAR_GC_DIR && File.join(MODULAR_GC_DIR, "librubygc.default.#{DLEXT}")
|
||||
|
||||
def setup
|
||||
super
|
||||
omit "Ruby was not configured with --with-modular-gc" if MODULAR_GC_DIR.nil? || MODULAR_GC_DIR.empty?
|
||||
omit "default modular GC is not installed" unless DEFAULT_GC && File.file?(DEFAULT_GC)
|
||||
end
|
||||
|
||||
%w[
|
||||
gc-mark-begin
|
||||
gc-mark-end
|
||||
gc-sweep-begin
|
||||
gc-sweep-end
|
||||
].each do |probe_name|
|
||||
define_method(:"test_modular_#{probe_name.gsub(/-/, '_')}") do
|
||||
probe = "ruby$target:::#{probe_name} { printf(\"#{probe_name}\\n\"); }"
|
||||
|
||||
trap_probe(probe, ruby_program, env: {"RUBY_GC_LIBRARY" => "default"}) do |_, _, saw|
|
||||
assert_operator saw.length, :>, 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ruby_program
|
||||
"100000.times { Object.new }"
|
||||
end
|
||||
end
|
||||
end if defined?(DTrace::TestCase)
|
||||
Reference in new issue
Block a user