Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/harness.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def get_rss
# First, grab a line like "62796 kB". Checking the Linux kernel source, Rss will always be in kB.
rss_desc = File.read(mem_rollup_file).lines.detect { |line| line.start_with?("Rss") }.split(":", 2)[1][/(\d+)/, 1]
1024 * Integer(rss_desc)
elsif defined?(WindowsMemoryCounters)
WindowsMemoryCounters.read&.fetch(:working_set) || 0
else
# Collect our own peak mem usage as soon as reasonable after finishing the last iteration.
# This method is only accurate to kilobytes, but is nicely portable and doesn't require
Expand Down Expand Up @@ -163,7 +165,12 @@ def macos_resident_size
buffer[RI_RESIDENT_SIZE_OFFSET, 8].unpack1('Q')
end

# Loaded after load_fiddle is defined, since it calls it right away.
require_relative "harness/windows_memory_counters" if RUBY_PLATFORM.match?(/mswin|mingw/)

def get_maxrss
return WindowsMemoryCounters.read&.fetch(:peak_working_set) if defined?(WindowsMemoryCounters)

load_fiddle

require 'rbconfig/sizeof'
Expand Down Expand Up @@ -221,6 +228,10 @@ def return_results(warmup_iterations, bench_iterations, **extra)
if maxrss = get_maxrss
ruby_bench_results["maxrss"] = maxrss
end
if defined?(WindowsMemoryCounters) && (counters = WindowsMemoryCounters.read)
ruby_bench_results["commit_charge"] = counters[:commit_charge]
ruby_bench_results["peak_commit_charge"] = counters[:peak_commit_charge]
end

# If YJIT or ZJIT is enabled, show some of its stats unless it does by itself.
if yjit_stats
Expand Down
34 changes: 34 additions & 0 deletions lib/harness/windows_memory_counters.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

# Resolved when the harness loads, before use_gemfile. Loading fiddle after Bundler.setup goes through Bundler.reset!,
# which redefines Kernel#gem and invalidates YJIT code if it happens between iterations. A dropped Fiddle::Handle is
# freed by a deferred finalizer, whose interrupt also sends YJIT code back to the interpreter, so kernel32 stays open.
module WindowsMemoryCounters
begin
load_fiddle
@kernel32 = Fiddle.dlopen("kernel32")
@process = Fiddle::Function.new(@kernel32['GetCurrentProcess'], [], Fiddle::TYPE_VOIDP).call
@get_info = Fiddle::Function.new(@kernel32['K32GetProcessMemoryInfo'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT], Fiddle::TYPE_INT)
rescue LoadError, Fiddle::DLError, NameError
warn "Failed to get memory counters: #{$!.message}"
end

# Memory counters of this process in bytes. The working set is the Windows counterpart of RSS,
# and the commit charge (PagefileUsage) is the private memory the process has committed.
def self.read
return nil unless @get_info

# PROCESS_MEMORY_COUNTERS: two DWORDs followed by eight SIZE_Ts.
buffer = "\0".b * (8 + 8 * Fiddle::SIZEOF_SIZE_T)
buffer[0, 4] = [buffer.bytesize].pack('L')
return nil if @get_info.call(@process, buffer, buffer.bytesize).zero?

peak_working_set, working_set, _, _, _, _, commit_charge, peak_commit_charge = buffer.unpack('x8J8')
{
working_set: working_set,
peak_working_set: peak_working_set,
commit_charge: commit_charge,
peak_commit_charge: peak_commit_charge,
}
end
end
Loading