diff --git a/harness/harness-common.rb b/harness/harness-common.rb index 073edb04..d1cd6051 100644 --- a/harness/harness-common.rb +++ b/harness/harness-common.rb @@ -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 is_windows + get_windows_memory_counters&.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 @@ -176,6 +178,46 @@ def self.function(name, args, ret) end end +# Memoized like ProcPidRusage, since load_fiddle alone takes about 0.1 ms on Windows. +module WindowsMemoryCounters + @resolved = false + + def self.fn + return @fn if @resolved + + @resolved = true + @fn = begin + load_fiddle + process = WindowsKernel32.function('GetCurrentProcess', [], Fiddle::TYPE_VOIDP).call + get_info = WindowsKernel32.function('K32GetProcessMemoryInfo', [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT], Fiddle::TYPE_INT) + ->(buffer) { get_info.call(process, buffer, buffer.bytesize) } + rescue LoadError, Fiddle::DLError, NameError + warn "Failed to get memory counters: #{$!.message}" + nil + end + end +end + +# Returns 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 get_windows_memory_counters + get_info = WindowsMemoryCounters.fn + 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(buffer).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 + def pin_to_cpu(cpu) load_fiddle # Etc.nprocessors counts the CPUs of every processor group, but the mask holds those of one group only. @@ -210,6 +252,8 @@ def raise_priority end def get_maxrss + return get_windows_memory_counters&.fetch(:peak_working_set) if is_windows + load_fiddle require 'rbconfig/sizeof' @@ -267,6 +311,10 @@ def return_results(warmup_iterations, bench_iterations, **extra) if maxrss = get_maxrss ruby_bench_results["maxrss"] = maxrss end + if is_windows && (counters = get_windows_memory_counters) + 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