From a2a9703dd1cfae3a04bb3bacbfb723f0bf29efd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 18 Nov 2025 00:51:35 +0000 Subject: [PATCH 1/8] Move the check_call method to BenchmarkRunner module And add tests to it. --- lib/benchmark_runner.rb | 17 ++++++++ run_benchmarks.rb | 31 ++++---------- test/benchmark_runner_test.rb | 76 +++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 24 deletions(-) diff --git a/lib/benchmark_runner.rb b/lib/benchmark_runner.rb index a19846b1..4cc4a886 100644 --- a/lib/benchmark_runner.rb +++ b/lib/benchmark_runner.rb @@ -80,4 +80,21 @@ def setarch_prefix prefix end + + # Checked system - error or return info if the command fails + def check_call(command, env: {}, raise_error: true, quiet: false) + puts("+ #{command}") unless quiet + + result = {} + + result[:success] = system(env, command) + result[:status] = $? + + unless result[:success] + puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}" + raise RuntimeError.new if raise_error + end + + result + end end diff --git a/run_benchmarks.rb b/run_benchmarks.rb index 4bc60915..88d49034 100755 --- a/run_benchmarks.rb +++ b/run_benchmarks.rb @@ -15,23 +15,6 @@ require_relative 'lib/table_formatter' require_relative 'lib/benchmark_filter' -# Checked system - error or return info if the command fails -def check_call(command, env: {}, raise_error: true, quiet: false) - puts("+ #{command}") unless quiet - - result = {} - - result[:success] = system(env, command) - result[:status] = $? - - unless result[:success] - puts "Command #{command.inspect} failed with exit code #{result[:status].exitstatus} in directory #{Dir.pwd}" - raise RuntimeError.new if raise_error - end - - result -end - def check_output(*command) IO.popen(*command, &:read) end @@ -46,17 +29,17 @@ def set_bench_config(turbo:) # sudo requires the flag '-S' in order to take input from stdin if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel unless intel_no_turbo? || turbo - check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } + BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } end # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? + BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD unless amd_no_boost? || turbo - check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } end - check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? end end @@ -230,7 +213,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat end # Do the benchmarking - result = check_call(cmd.shelljoin, env: env, raise_error: false) + result = BenchmarkRunner.check_call(cmd.shelljoin, env: env, raise_error: false) if result[:success] bench_data[bench_name] = JSON.parse(File.read(result_json_path)).tap do |json| diff --git a/test/benchmark_runner_test.rb b/test/benchmark_runner_test.rb index 1633c141..e44c2c0f 100644 --- a/test/benchmark_runner_test.rb +++ b/test/benchmark_runner_test.rb @@ -170,6 +170,82 @@ end end + describe '.check_call' do + it 'runs a successful command and returns success status' do + result = nil + + capture_io do + result = BenchmarkRunner.check_call('true') + end + + assert_equal true, result[:success] + assert_equal 0, result[:status].exitstatus + end + + it 'prints the command by default' do + output = capture_io do + BenchmarkRunner.check_call('true') + end + + assert_includes output[0], '+ true' + end + + it 'suppresses output when quiet is true' do + output = capture_io do + BenchmarkRunner.check_call('true', quiet: true) + end + + assert_equal '', output[0] + end + + it 'raises error by default when command fails' do + output = capture_io do + assert_raises(RuntimeError) do + BenchmarkRunner.check_call('false') + end + end + + assert_includes output[0], 'Command "false" failed' + end + + it 'does not raise error when raise_error is false' do + output = capture_io do + result = BenchmarkRunner.check_call('false', raise_error: false) + + assert_equal false, result[:success] + assert_equal 1, result[:status].exitstatus + end + + assert_includes output[0], 'Command "false" failed' + end + + it 'passes environment variables to the command' do + Dir.mktmpdir do |dir| + output_file = File.join(dir, 'test_output.txt') + output = capture_io do + result = BenchmarkRunner.check_call("sh -c 'echo $TEST_VAR > #{output_file}'", env: { 'TEST_VAR' => 'hello' }) + assert_equal true, result[:success] + end + + # Command should be printed + assert_includes output[0], "+ sh -c 'echo $TEST_VAR" + # Environment variable should be written to file + assert_equal "hello\n", File.read(output_file) + end + end + + it 'includes exit code and directory in error message' do + output = capture_io do + result = BenchmarkRunner.check_call('sh -c "exit 42"', raise_error: false) + assert_equal false, result[:success] + assert_equal 42, result[:status].exitstatus + end + + assert_includes output[0], 'exit code 42' + assert_includes output[0], "directory #{Dir.pwd}" + end + end + describe '.setarch_prefix' do it 'returns an array' do result = BenchmarkRunner.setarch_prefix From 69f4205a7ed03a10e18191dfc4c4ad2c3860ba38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 18 Nov 2025 00:54:02 +0000 Subject: [PATCH 2/8] Use backtrick instead of custom helper to get the result of system calls --- run_benchmarks.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/run_benchmarks.rb b/run_benchmarks.rb index 88d49034..fb2d9e00 100755 --- a/run_benchmarks.rb +++ b/run_benchmarks.rb @@ -15,12 +15,8 @@ require_relative 'lib/table_formatter' require_relative 'lib/benchmark_filter' -def check_output(*command) - IO.popen(*command, &:read) -end - def have_yjit?(ruby) - ruby_version = check_output("#{ruby} -v --yjit", err: File::NULL).strip + ruby_version = `#{ruby} -v --yjit 2> #{File::NULL}`.strip ruby_version.downcase.include?("yjit") end @@ -381,7 +377,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat ruby_descriptions = {} args.executables.each do |name, executable| - ruby_descriptions[name] = check_output([*executable, "-v"]).chomp + ruby_descriptions[name] = `#{executable.shelljoin} -v`.chomp end # Benchmark with and without YJIT From 69a4ee55b6818bf5cc4ddb04e5d85b097b2c6774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 18 Nov 2025 01:50:09 +0000 Subject: [PATCH 3/8] Extract CPU configuration logic into its own file And add tests for it. --- lib/cpu_config.rb | 83 +++++++++++ run_benchmarks.rb | 72 +-------- test/cpu_config_test.rb | 322 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 407 insertions(+), 70 deletions(-) create mode 100644 lib/cpu_config.rb create mode 100644 test/cpu_config_test.rb diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb new file mode 100644 index 00000000..59e5b7dd --- /dev/null +++ b/lib/cpu_config.rb @@ -0,0 +1,83 @@ +require_relative 'benchmark_runner' + +# Manages CPU frequency and turbo boost configuration for benchmark consistency +class CPUConfig + class << self + # Configure CPU for benchmarking: disable frequency scaling and verify settings + def configure_for_benchmarking(turbo:) + disable_frequency_scaling(turbo: turbo) + check_pstate(turbo: turbo) + end + + private + + # Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. + def disable_frequency_scaling(turbo:) + # sudo requires the flag '-S' in order to take input from stdin + if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel + unless intel_no_turbo? || turbo + BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } + end + # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. + BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? + elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD + unless amd_no_boost? || turbo + BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + end + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? + end + end + + def intel_no_turbo? + File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && + File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' + end + + def intel_perf_100pct? + File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && + File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' + end + + def amd_no_boost? + File.exist?('/sys/devices/system/cpu/cpufreq/boost') && + File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' + end + + def performance_governor? + Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| + File.read(governor).strip == 'performance' + end + end + + # Verify that CPU frequency settings have been configured correctly + def check_pstate(turbo:) + if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel + unless turbo || intel_no_turbo? + puts("You forgot to disable turbo:") + puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + exit(-1) + end + + unless intel_perf_100pct? + puts("You forgot to set the min perf percentage to 100:") + puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + exit(-1) + end + elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD + unless turbo || amd_no_boost? + puts("You forgot to disable boost:") + puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + exit(-1) + end + + unless performance_governor? + puts("You forgot to set the performance governor:") + puts(" sudo cpupower frequency-set -g performance") + exit(-1) + end + end + end + end +end diff --git a/run_benchmarks.rb b/run_benchmarks.rb index fb2d9e00..5faa2f71 100755 --- a/run_benchmarks.rb +++ b/run_benchmarks.rb @@ -11,6 +11,7 @@ require 'etc' require 'yaml' require_relative 'misc/stats' +require_relative 'lib/cpu_config' require_relative 'lib/benchmark_runner' require_relative 'lib/table_formatter' require_relative 'lib/benchmark_filter' @@ -20,71 +21,6 @@ def have_yjit?(ruby) ruby_version.downcase.include?("yjit") end -# Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. -def set_bench_config(turbo:) - # sudo requires the flag '-S' in order to take input from stdin - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless intel_no_turbo? || turbo - BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } - end - # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless amd_no_boost? || turbo - BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } - end - BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? - end -end - -def check_pstate(turbo:) - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless turbo || intel_no_turbo? - puts("You forgot to disable turbo:") - puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - exit(-1) - end - - unless intel_perf_100pct? - puts("You forgot to set the min perf percentage to 100:") - puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") - exit(-1) - end - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless turbo || amd_no_boost? - puts("You forgot to disable boost:") - puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - exit(-1) - end - - unless performance_governor? - puts("You forgot to set the performance governor:") - puts(" sudo cpupower frequency-set -g performance") - exit(-1) - end - end -end - -def intel_no_turbo? - File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' -end - -def intel_perf_100pct? - File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' -end - -def amd_no_boost? - File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' -end - -def performance_governor? - Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| - File.read(governor).strip == 'performance' - end -end - def mean(values) Stats.new(values).mean end @@ -366,11 +302,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat end end -# Disable CPU frequency scaling -set_bench_config(turbo: args.turbo) - -# Check pstate status -check_pstate(turbo: args.turbo) +CPUConfig.configure_for_benchmarking(turbo: args.turbo) # Create the output directory FileUtils.mkdir_p(args.out_path) diff --git a/test/cpu_config_test.rb b/test/cpu_config_test.rb new file mode 100644 index 00000000..e94ecb56 --- /dev/null +++ b/test/cpu_config_test.rb @@ -0,0 +1,322 @@ +require_relative 'test_helper' +require_relative '../lib/cpu_config' +require_relative '../lib/benchmark_runner' + +describe CPUConfig do + describe '.configure_for_benchmarking' do + it 'does nothing when CPU frequency files do not exist' do + call_count = 0 + at_exit_called = false + exit_called = false + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + File.stub :exist?, false do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when files don't exist" + refute at_exit_called, "Should not call at_exit when CPU frequency files don't exist" + refute exit_called, "Should not exit when files don't exist" + end + end + end + end + end + + it 'does not call commands or exit when Intel CPU is already properly configured with turbo disabled' do + call_count = 0 + at_exit_called = false + exit_called = false + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "1\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when Intel CPU is properly configured" + refute at_exit_called, "Should not call at_exit when Intel CPU already configured" + refute exit_called, "Should not exit when Intel CPU is properly configured" + end + end + end + end + end + end + + it 'does not call commands or exit when Intel CPU allows turbo and min_perf is 100%' do + call_count = 0 + at_exit_called = false + exit_called = false + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "0\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: true) + end + assert_equal 0, call_count, "Should not call check_call when turbo is true and min_perf is correct" + refute at_exit_called, "Should not call at_exit when turbo is true and CPU already configured" + refute exit_called, "Should not exit when turbo is true and performance is correct" + end + end + end + end + end + end + + it 'configures Intel CPU and registers cleanup when turbo needs to be disabled' do + call_count = 0 + at_exit_called = false + at_exit_block = nil + exit_called = false + read_count = 0 + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + read_count += 1 + # First read checks if turbo is disabled (for disable_frequency_scaling) + # Second read checks if turbo is disabled (for check_pstate) + # After the first check_call, we simulate that turbo is now disabled + read_count <= 1 ? "0\n" : "1\n" + elsif path.include?('min_perf_pct') + # After check_call sets it, simulate it's now 100% + "100\n" + end + } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: false) + end + assert_operator call_count, :>, 0, "Should call check_call to configure Intel CPU" + assert at_exit_called, "Should register at_exit handler to restore CPU settings" + assert_instance_of Proc, at_exit_block, "at_exit should be called with a block" + refute exit_called, "Should not exit when Intel CPU gets properly configured" + end + end + end + end + end + + # Verify at_exit block restores Intel turbo settings + cleanup_commands = [] + BenchmarkRunner.stub :check_call, ->(cmd, **opts) { cleanup_commands << { cmd: cmd, opts: opts } } do + at_exit_block.call + end + + assert_equal 1, cleanup_commands.length, "at_exit block should call check_call once" + assert_equal "sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", cleanup_commands[0][:cmd] + assert_equal({ quiet: true }, cleanup_commands[0][:opts]) + end + + it 'exits when Intel turbo is not disabled and turbo flag is false' do + exit_code = nil + output = capture_io do + CPUConfig.stub :at_exit, ->(&block) {} do + CPUConfig.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "0\n" + elsif path.include?('min_perf_pct') + "100\n" + end + } do + CPUConfig.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to disable turbo" + assert_includes output[0], "sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'" + end + + it 'exits when Intel min perf is not 100%' do + exit_code = nil + output = capture_io do + CPUConfig.stub :at_exit, ->(&block) {} do + CPUConfig.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :read, lambda { |path| + if path.include?('no_turbo') + "1\n" + elsif path.include?('min_perf_pct') + "50\n" + end + } do + CPUConfig.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to set the min perf percentage to 100" + assert_includes output[0], "sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'" + end + + it 'does not call commands or exit when AMD CPU is already properly configured with turbo disabled' do + call_count = 0 + at_exit_called = false + exit_called = false + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :read, ->(path) { + if path.include?('boost') + "0\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: false) + end + assert_equal 0, call_count, "Should not call check_call when AMD CPU is properly configured" + refute at_exit_called, "Should not call at_exit when AMD CPU already configured" + refute exit_called, "Should not exit when AMD CPU is properly configured" + end + end + end + end + end + end + end + + it 'configures AMD CPU and registers cleanup when boost needs to be disabled' do + call_count = 0 + at_exit_called = false + at_exit_block = nil + exit_called = false + read_count = 0 + + CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + CPUConfig.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :read, lambda { |path| + if path.include?('boost') + read_count += 1 + # First read checks if boost is disabled, second read is after we disable it + read_count == 1 ? "1\n" : "0\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + capture_io do + CPUConfig.configure_for_benchmarking(turbo: false) + end + assert_operator call_count, :>, 0, "Should call check_call to configure AMD CPU" + assert at_exit_called, "Should register at_exit handler to restore CPU settings" + assert_instance_of Proc, at_exit_block, "at_exit should be called with a block" + refute exit_called, "Should not exit when AMD CPU gets properly configured" + end + end + end + end + end + end + + # Verify at_exit block restores AMD boost settings + cleanup_commands = [] + BenchmarkRunner.stub :check_call, ->(cmd, **opts) { cleanup_commands << { cmd: cmd, opts: opts } } do + at_exit_block.call + end + + assert_equal 1, cleanup_commands.length, "at_exit block should call check_call once" + assert_equal "sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", cleanup_commands[0][:cmd] + assert_equal({ quiet: true }, cleanup_commands[0][:opts]) + end + + it 'exits when AMD boost is not disabled and turbo flag is false' do + exit_code = nil + output = capture_io do + CPUConfig.stub :at_exit, ->(&block) {} do + CPUConfig.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :read, ->(path) { + if path.include?('boost') + "1\n" + else + "performance\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + CPUConfig.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to disable boost" + assert_includes output[0], "sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'" + end + + it 'exits when AMD performance governor is not set' do + exit_code = nil + output = capture_io do + CPUConfig.stub :at_exit, ->(&block) {} do + CPUConfig.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :read, lambda { |path| + if path.include?('boost') + "0\n" + else + "powersave\n" + end + } do + Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do + CPUConfig.configure_for_benchmarking(turbo: false) + end + end + end + end + end + end + end + + assert_equal(-1, exit_code) + assert_includes output[0], "You forgot to set the performance governor" + assert_includes output[0], "sudo cpupower frequency-set -g performance" + end + end +end From 977039d86a0614d109728f643630ccb31b85d0ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 18 Nov 2025 01:51:11 +0000 Subject: [PATCH 4/8] Inline setarch_prefix --- run_benchmarks.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/run_benchmarks.rb b/run_benchmarks.rb index 5faa2f71..55d32693 100755 --- a/run_benchmarks.rb +++ b/run_benchmarks.rb @@ -47,10 +47,6 @@ def sort_benchmarks(bench_names) BenchmarkRunner.sort_benchmarks(bench_names, benchmarks_metadata) end -def setarch_prefix - BenchmarkRunner.setarch_prefix -end - # Run all the benchmarks and record execution times def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_path:, harness:, pre_init:, no_pinning:) bench_data = {} @@ -108,7 +104,7 @@ def run_benchmarks(ruby:, ruby_description:, categories:, name_filters:, out_pat # Set up the benchmarking command cmd = [] if BenchmarkRunner.os == :linux - cmd += setarch_prefix + cmd += BenchmarkRunner.setarch_prefix # Pin the process to one given core to improve caching and reduce variance on CRuby # Other Rubies need to use multiple cores, e.g., for JIT threads From fd242a73218d0180928c2f70f3b7f3c36aecad07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 20 Nov 2025 17:02:41 +0000 Subject: [PATCH 5/8] Refactor disable_frequency_scaling to multiple methods with meaningful names --- lib/cpu_config.rb | 53 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb index 59e5b7dd..4d07fb67 100644 --- a/lib/cpu_config.rb +++ b/lib/cpu_config.rb @@ -13,23 +13,48 @@ def configure_for_benchmarking(turbo:) # Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. def disable_frequency_scaling(turbo:) - # sudo requires the flag '-S' in order to take input from stdin - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless intel_no_turbo? || turbo - BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } - end - # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") unless intel_perf_100pct? - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless amd_no_boost? || turbo - BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } - end - BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") unless performance_governor? + if intel_cpu? + disable_intel_turbo unless turbo + maximize_intel_frequency + elsif amd_cpu? + disable_amd_boost unless turbo + set_performance_governor end end + def intel_cpu? + File.exist?('/sys/devices/system/cpu/intel_pstate') + end + + def amd_cpu? + File.exist?('/sys/devices/system/cpu/cpufreq/boost') + end + + def disable_intel_turbo + return if intel_no_turbo? + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } + end + + def maximize_intel_frequency + return if intel_perf_100pct? + # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. + BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + end + + def disable_amd_boost + return if amd_no_boost? + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + end + + def set_performance_governor + return if performance_governor? + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") + end + def intel_no_turbo? File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' From 81fc27da78366e18ad093b6c26024912ac010227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 20 Nov 2025 17:41:07 +0000 Subject: [PATCH 6/8] Move the logic to the instance --- lib/cpu_config.rb | 175 +++++++++++++++++++++------------------- test/cpu_config_test.rb | 110 +++++++++++++------------ 2 files changed, 151 insertions(+), 134 deletions(-) diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb index 4d07fb67..015cd6fd 100644 --- a/lib/cpu_config.rb +++ b/lib/cpu_config.rb @@ -2,106 +2,113 @@ # Manages CPU frequency and turbo boost configuration for benchmark consistency class CPUConfig - class << self - # Configure CPU for benchmarking: disable frequency scaling and verify settings - def configure_for_benchmarking(turbo:) - disable_frequency_scaling(turbo: turbo) - check_pstate(turbo: turbo) - end + # Configure CPU for benchmarking: disable frequency scaling and verify settings + def self.configure_for_benchmarking(turbo:) + new.configure_for_benchmarking(turbo: turbo) + end - private + def initialize + @intel_cpu = File.exist?('/sys/devices/system/cpu/intel_pstate') + @amd_cpu = File.exist?('/sys/devices/system/cpu/cpufreq/boost') + end - # Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. - def disable_frequency_scaling(turbo:) - if intel_cpu? - disable_intel_turbo unless turbo - maximize_intel_frequency - elsif amd_cpu? - disable_amd_boost unless turbo - set_performance_governor - end - end + def configure_for_benchmarking(turbo:) + disable_frequency_scaling(turbo: turbo) + check_pstate(turbo: turbo) + end - def intel_cpu? - File.exist?('/sys/devices/system/cpu/intel_pstate') - end + private - def amd_cpu? - File.exist?('/sys/devices/system/cpu/cpufreq/boost') + # Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. + def disable_frequency_scaling(turbo:) + if intel_cpu? + disable_intel_turbo unless turbo + maximize_intel_frequency + elsif amd_cpu? + disable_amd_boost unless turbo + set_performance_governor end + end - def disable_intel_turbo - return if intel_no_turbo? - # sudo requires the flag '-S' in order to take input from stdin - BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } - end + def intel_cpu? + @intel_cpu + end - def maximize_intel_frequency - return if intel_perf_100pct? - # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") - end + def amd_cpu? + @amd_cpu + end - def disable_amd_boost - return if amd_no_boost? - # sudo requires the flag '-S' in order to take input from stdin - BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } - end + def disable_intel_turbo + return if intel_no_turbo? + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } + end - def set_performance_governor - return if performance_governor? - BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") - end + def maximize_intel_frequency + return if intel_perf_100pct? + # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. + BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + end - def intel_no_turbo? - File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && - File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' - end + def disable_amd_boost + return if amd_no_boost? + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + end - def intel_perf_100pct? - File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && - File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' - end + def set_performance_governor + return if performance_governor? + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") + end + + def intel_no_turbo? + File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && + File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' + end + + def intel_perf_100pct? + File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && + File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' + end - def amd_no_boost? - File.exist?('/sys/devices/system/cpu/cpufreq/boost') && - File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' + def amd_no_boost? + File.exist?('/sys/devices/system/cpu/cpufreq/boost') && + File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' + end + + def performance_governor? + Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| + File.read(governor).strip == 'performance' end + end - def performance_governor? - Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| - File.read(governor).strip == 'performance' + # Verify that CPU frequency settings have been configured correctly + def check_pstate(turbo:) + if intel_cpu? + unless turbo || intel_no_turbo? + puts("You forgot to disable turbo:") + puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + exit(-1) + end + + unless intel_perf_100pct? + puts("You forgot to set the min perf percentage to 100:") + puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + exit(-1) + end + elsif amd_cpu? + unless turbo || amd_no_boost? + puts("You forgot to disable boost:") + puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + exit(-1) end - end - # Verify that CPU frequency settings have been configured correctly - def check_pstate(turbo:) - if File.exist?('/sys/devices/system/cpu/intel_pstate') # Intel - unless turbo || intel_no_turbo? - puts("You forgot to disable turbo:") - puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - exit(-1) - end - - unless intel_perf_100pct? - puts("You forgot to set the min perf percentage to 100:") - puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") - exit(-1) - end - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') # AMD - unless turbo || amd_no_boost? - puts("You forgot to disable boost:") - puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - exit(-1) - end - - unless performance_governor? - puts("You forgot to set the performance governor:") - puts(" sudo cpupower frequency-set -g performance") - exit(-1) - end + unless performance_governor? + puts("You forgot to set the performance governor:") + puts(" sudo cpupower frequency-set -g performance") + exit(-1) end end end diff --git a/test/cpu_config_test.rb b/test/cpu_config_test.rb index e94ecb56..08cfd236 100644 --- a/test/cpu_config_test.rb +++ b/test/cpu_config_test.rb @@ -3,18 +3,19 @@ require_relative '../lib/benchmark_runner' describe CPUConfig do - describe '.configure_for_benchmarking' do + describe '#configure_for_benchmarking' do it 'does nothing when CPU frequency files do not exist' do call_count = 0 at_exit_called = false exit_called = false - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - File.stub :exist?, false do + File.stub :exist?, false do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end assert_equal 0, call_count, "Should not call check_call when files don't exist" refute at_exit_called, "Should not call at_exit when CPU frequency files don't exist" @@ -30,10 +31,11 @@ at_exit_called = false exit_called = false - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do - File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do File.stub :read, lambda { |path| if path.include?('no_turbo') "1\n" @@ -42,7 +44,7 @@ end } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end assert_equal 0, call_count, "Should not call check_call when Intel CPU is properly configured" refute at_exit_called, "Should not call at_exit when Intel CPU already configured" @@ -59,10 +61,11 @@ at_exit_called = false exit_called = false - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do - File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do File.stub :read, lambda { |path| if path.include?('no_turbo') "0\n" @@ -71,7 +74,7 @@ end } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: true) + cpu_config.configure_for_benchmarking(turbo: true) end assert_equal 0, call_count, "Should not call check_call when turbo is true and min_perf is correct" refute at_exit_called, "Should not call at_exit when turbo is true and CPU already configured" @@ -90,10 +93,11 @@ exit_called = false read_count = 0 - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do - File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do File.stub :read, lambda { |path| if path.include?('no_turbo') read_count += 1 @@ -107,7 +111,7 @@ end } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end assert_operator call_count, :>, 0, "Should call check_call to configure Intel CPU" assert at_exit_called, "Should register at_exit handler to restore CPU settings" @@ -133,10 +137,11 @@ it 'exits when Intel turbo is not disabled and turbo flag is false' do exit_code = nil output = capture_io do - CPUConfig.stub :at_exit, ->(&block) {} do - CPUConfig.stub :exit, ->(code) { exit_code = code } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do - File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do File.stub :read, lambda { |path| if path.include?('no_turbo') "0\n" @@ -144,7 +149,7 @@ "100\n" end } do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end end end @@ -160,10 +165,11 @@ it 'exits when Intel min perf is not 100%' do exit_code = nil output = capture_io do - CPUConfig.stub :at_exit, ->(&block) {} do - CPUConfig.stub :exit, ->(code) { exit_code = code } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do - File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do File.stub :read, lambda { |path| if path.include?('no_turbo') "1\n" @@ -171,7 +177,7 @@ "50\n" end } do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end end end @@ -189,10 +195,11 @@ at_exit_called = false exit_called = false - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do - File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do File.stub :read, ->(path) { if path.include?('boost') "0\n" @@ -202,7 +209,7 @@ } do Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end assert_equal 0, call_count, "Should not call check_call when AMD CPU is properly configured" refute at_exit_called, "Should not call at_exit when AMD CPU already configured" @@ -222,10 +229,11 @@ exit_called = false read_count = 0 - CPUConfig.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do - CPUConfig.stub :exit, ->(code) { exit_called = true } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do - File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do + cpu_config.stub :exit, ->(code) { exit_called = true } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do File.stub :read, lambda { |path| if path.include?('boost') read_count += 1 @@ -237,7 +245,7 @@ } do Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do capture_io do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end assert_operator call_count, :>, 0, "Should call check_call to configure AMD CPU" assert at_exit_called, "Should register at_exit handler to restore CPU settings" @@ -264,10 +272,11 @@ it 'exits when AMD boost is not disabled and turbo flag is false' do exit_code = nil output = capture_io do - CPUConfig.stub :at_exit, ->(&block) {} do - CPUConfig.stub :exit, ->(code) { exit_code = code } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do - File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do File.stub :read, ->(path) { if path.include?('boost') "1\n" @@ -276,7 +285,7 @@ end } do Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end end end @@ -293,10 +302,11 @@ it 'exits when AMD performance governor is not set' do exit_code = nil output = capture_io do - CPUConfig.stub :at_exit, ->(&block) {} do - CPUConfig.stub :exit, ->(code) { exit_code = code } do - BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do - File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + cpu_config = CPUConfig.new + cpu_config.stub :at_exit, ->(&block) {} do + cpu_config.stub :exit, ->(code) { exit_code = code } do + BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do File.stub :read, lambda { |path| if path.include?('boost') "0\n" @@ -305,7 +315,7 @@ end } do Dir.stub :glob, ->(pattern) { ['/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor'] } do - CPUConfig.configure_for_benchmarking(turbo: false) + cpu_config.configure_for_benchmarking(turbo: false) end end end From 0da8673d19dcde0c11f177a5761632363113e5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 20 Nov 2025 17:56:03 +0000 Subject: [PATCH 7/8] Split CPU config into Intel and AMD classes --- lib/cpu_config.rb | 163 ++++++++++++++++++++++++---------------- test/cpu_config_test.rb | 53 ++++++++++--- 2 files changed, 141 insertions(+), 75 deletions(-) diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb index 015cd6fd..bc74976d 100644 --- a/lib/cpu_config.rb +++ b/lib/cpu_config.rb @@ -2,14 +2,21 @@ # Manages CPU frequency and turbo boost configuration for benchmark consistency class CPUConfig - # Configure CPU for benchmarking: disable frequency scaling and verify settings - def self.configure_for_benchmarking(turbo:) - new.configure_for_benchmarking(turbo: turbo) - end + class << self + # Configure CPU for benchmarking: disable frequency scaling and verify settings + def configure_for_benchmarking(turbo:) + build.configure_for_benchmarking(turbo: turbo) + end - def initialize - @intel_cpu = File.exist?('/sys/devices/system/cpu/intel_pstate') - @amd_cpu = File.exist?('/sys/devices/system/cpu/cpufreq/boost') + def build + if File.exist?('/sys/devices/system/cpu/intel_pstate') + IntelCPUConfig.new + elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') + AMDCPUConfig.new + else + NullCPUConfig.new + end + end end def configure_for_benchmarking(turbo:) @@ -19,97 +26,123 @@ def configure_for_benchmarking(turbo:) private - # Disable Turbo Boost while running benchmarks. Maximize the CPU frequency. def disable_frequency_scaling(turbo:) - if intel_cpu? - disable_intel_turbo unless turbo - maximize_intel_frequency - elsif amd_cpu? - disable_amd_boost unless turbo - set_performance_governor - end + disable_turbo_boost unless turbo || turbo_disabled? + maximize_frequency unless frequency_maximized? + end + + def turbo_disabled? + # Override in subclasses + false end - def intel_cpu? - @intel_cpu + def frequency_maximized? + # Override in subclasses + false end - def amd_cpu? - @amd_cpu + def disable_turbo_boost + # Override in subclasses end - def disable_intel_turbo - return if intel_no_turbo? + def maximize_frequency + # Override in subclasses + end + + def check_pstate(turbo:) + # Override in subclasses + end +end + +# Intel CPU configuration +class IntelCPUConfig < CPUConfig + private + + def disable_turbo_boost # sudo requires the flag '-S' in order to take input from stdin BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } end - def maximize_intel_frequency - return if intel_perf_100pct? + def maximize_frequency # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") end - def disable_amd_boost - return if amd_no_boost? - # sudo requires the flag '-S' in order to take input from stdin - BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + def turbo_disabled? + @turbo_disabled ||= File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && + File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' end - def set_performance_governor - return if performance_governor? - BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") + def frequency_maximized? + @frequency_maximized ||= File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && + File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' end - def intel_no_turbo? - File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && - File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' + def check_pstate(turbo:) + unless turbo || turbo_disabled? + puts("You forgot to disable turbo:") + puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + exit(-1) + end + + unless frequency_maximized? + puts("You forgot to set the min perf percentage to 100:") + puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + exit(-1) + end end +end - def intel_perf_100pct? - File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && - File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' +# AMD CPU configuration +class AMDCPUConfig < CPUConfig + private + + def disable_turbo_boost + # sudo requires the flag '-S' in order to take input from stdin + BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } end - def amd_no_boost? - File.exist?('/sys/devices/system/cpu/cpufreq/boost') && + def maximize_frequency + BenchmarkRunner.check_call("sudo -S cpupower frequency-set -g performance") + end + + def turbo_disabled? + @turbo_disabled ||= File.exist?('/sys/devices/system/cpu/cpufreq/boost') && File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' end - def performance_governor? - Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| + def frequency_maximized? + @frequency_maximized ||= Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| File.read(governor).strip == 'performance' end end - # Verify that CPU frequency settings have been configured correctly def check_pstate(turbo:) - if intel_cpu? - unless turbo || intel_no_turbo? - puts("You forgot to disable turbo:") - puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - exit(-1) - end - - unless intel_perf_100pct? - puts("You forgot to set the min perf percentage to 100:") - puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") - exit(-1) - end - elsif amd_cpu? - unless turbo || amd_no_boost? - puts("You forgot to disable boost:") - puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - exit(-1) - end + unless turbo || turbo_disabled? + puts("You forgot to disable boost:") + puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + exit(-1) + end - unless performance_governor? - puts("You forgot to set the performance governor:") - puts(" sudo cpupower frequency-set -g performance") - exit(-1) - end + unless frequency_maximized? + puts("You forgot to set the performance governor:") + puts(" sudo cpupower frequency-set -g performance") + exit(-1) end end end + +# Null object for unsupported CPUs +class NullCPUConfig < CPUConfig + private + + def disable_frequency_scaling(turbo:) + # Do nothing + end + + def check_pstate(turbo:) + # Do nothing + end +end diff --git a/test/cpu_config_test.rb b/test/cpu_config_test.rb index 08cfd236..f59a28e9 100644 --- a/test/cpu_config_test.rb +++ b/test/cpu_config_test.rb @@ -3,6 +3,31 @@ require_relative '../lib/benchmark_runner' describe CPUConfig do + describe '.build' do + it 'returns IntelCPUConfig when Intel pstate files exist' do + File.stub :exist?, ->(path) { path.include?('intel_pstate') } do + config = CPUConfig.build + assert_instance_of IntelCPUConfig, config + end + end + + it 'returns AMDCPUConfig when AMD cpufreq files exist' do + File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do + config = CPUConfig.build + assert_instance_of AMDCPUConfig, config + end + end + + it 'returns NullCPUConfig when no CPU files exist' do + File.stub :exist?, false do + config = CPUConfig.build + assert_instance_of NullCPUConfig, config + end + end + end +end + +describe NullCPUConfig do describe '#configure_for_benchmarking' do it 'does nothing when CPU frequency files do not exist' do call_count = 0 @@ -10,7 +35,7 @@ exit_called = false File.stub :exist?, false do - cpu_config = CPUConfig.new + cpu_config = NullCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -25,14 +50,18 @@ end end end + end +end +describe IntelCPUConfig do + describe '#configure_for_benchmarking' do it 'does not call commands or exit when Intel CPU is already properly configured with turbo disabled' do call_count = 0 at_exit_called = false exit_called = false File.stub :exist?, ->(path) { path.include?('intel_pstate') } do - cpu_config = CPUConfig.new + cpu_config = IntelCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -62,7 +91,7 @@ exit_called = false File.stub :exist?, ->(path) { path.include?('intel_pstate') } do - cpu_config = CPUConfig.new + cpu_config = IntelCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -94,7 +123,7 @@ read_count = 0 File.stub :exist?, ->(path) { path.include?('intel_pstate') } do - cpu_config = CPUConfig.new + cpu_config = IntelCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -138,7 +167,7 @@ exit_code = nil output = capture_io do File.stub :exist?, ->(path) { path.include?('intel_pstate') } do - cpu_config = CPUConfig.new + cpu_config = IntelCPUConfig.new cpu_config.stub :at_exit, ->(&block) {} do cpu_config.stub :exit, ->(code) { exit_code = code } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do @@ -166,7 +195,7 @@ exit_code = nil output = capture_io do File.stub :exist?, ->(path) { path.include?('intel_pstate') } do - cpu_config = CPUConfig.new + cpu_config = IntelCPUConfig.new cpu_config.stub :at_exit, ->(&block) {} do cpu_config.stub :exit, ->(code) { exit_code = code } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do @@ -189,14 +218,18 @@ assert_includes output[0], "You forgot to set the min perf percentage to 100" assert_includes output[0], "sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'" end + end +end +describe AMDCPUConfig do + describe '#configure_for_benchmarking' do it 'does not call commands or exit when AMD CPU is already properly configured with turbo disabled' do call_count = 0 at_exit_called = false exit_called = false File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do - cpu_config = CPUConfig.new + cpu_config = AMDCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -230,7 +263,7 @@ read_count = 0 File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do - cpu_config = CPUConfig.new + cpu_config = AMDCPUConfig.new cpu_config.stub :at_exit, ->(&block) { at_exit_called = true; at_exit_block = block } do cpu_config.stub :exit, ->(code) { exit_called = true } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) { call_count += 1 } do @@ -273,7 +306,7 @@ exit_code = nil output = capture_io do File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do - cpu_config = CPUConfig.new + cpu_config = AMDCPUConfig.new cpu_config.stub :at_exit, ->(&block) {} do cpu_config.stub :exit, ->(code) { exit_code = code } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do @@ -303,7 +336,7 @@ exit_code = nil output = capture_io do File.stub :exist?, ->(path) { path.include?('cpufreq/boost') } do - cpu_config = CPUConfig.new + cpu_config = AMDCPUConfig.new cpu_config.stub :at_exit, ->(&block) {} do cpu_config.stub :exit, ->(code) { exit_code = code } do BenchmarkRunner.stub :check_call, ->(*_args, **_kwargs) {} do From 68a4f3bcd3446d74d60f6b461c13c8f7c7bc2867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 20 Nov 2025 17:59:52 +0000 Subject: [PATCH 8/8] Extract constants to avoid duplication and improve maintainability --- lib/cpu_config.rb | 51 +++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/cpu_config.rb b/lib/cpu_config.rb index bc74976d..0ac01688 100644 --- a/lib/cpu_config.rb +++ b/lib/cpu_config.rb @@ -9,9 +9,9 @@ def configure_for_benchmarking(turbo:) end def build - if File.exist?('/sys/devices/system/cpu/intel_pstate') + if File.exist?(IntelCPUConfig::PSTATE_DIR) IntelCPUConfig.new - elsif File.exist?('/sys/devices/system/cpu/cpufreq/boost') + elsif File.exist?(AMDCPUConfig::BOOST_PATH) AMDCPUConfig.new else NullCPUConfig.new @@ -56,39 +56,45 @@ def check_pstate(turbo:) # Intel CPU configuration class IntelCPUConfig < CPUConfig + PSTATE_DIR = '/sys/devices/system/cpu/intel_pstate' + NO_TURBO_PATH = "#{PSTATE_DIR}/no_turbo" + MIN_PERF_PCT_PATH = "#{PSTATE_DIR}/min_perf_pct" + TURBO_DISABLED_VALUE = '1' + FREQUENCY_MAXIMIZED_VALUE = '100' + private def disable_turbo_boost # sudo requires the flag '-S' in order to take input from stdin - BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo'", quiet: true) } + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > #{NO_TURBO_PATH}'", quiet: true) } end def maximize_frequency # Disabling Turbo Boost reduces the CPU frequency, so this should be run after that. - BenchmarkRunner.check_call("sudo -S sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'") end def turbo_disabled? - @turbo_disabled ||= File.exist?('/sys/devices/system/cpu/intel_pstate/no_turbo') && - File.read('/sys/devices/system/cpu/intel_pstate/no_turbo').strip == '1' + @turbo_disabled ||= File.exist?(NO_TURBO_PATH) && + File.read(NO_TURBO_PATH).strip == TURBO_DISABLED_VALUE end def frequency_maximized? - @frequency_maximized ||= File.exist?('/sys/devices/system/cpu/intel_pstate/min_perf_pct') && - File.read('/sys/devices/system/cpu/intel_pstate/min_perf_pct').strip == '100' + @frequency_maximized ||= File.exist?(MIN_PERF_PCT_PATH) && + File.read(MIN_PERF_PCT_PATH).strip == FREQUENCY_MAXIMIZED_VALUE end def check_pstate(turbo:) unless turbo || turbo_disabled? puts("You forgot to disable turbo:") - puts(" sudo sh -c 'echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo'") + puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{NO_TURBO_PATH}'") exit(-1) end unless frequency_maximized? puts("You forgot to set the min perf percentage to 100:") - puts(" sudo sh -c 'echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct'") + puts(" sudo sh -c 'echo #{FREQUENCY_MAXIMIZED_VALUE} > #{MIN_PERF_PCT_PATH}'") exit(-1) end end @@ -96,12 +102,19 @@ def check_pstate(turbo:) # AMD CPU configuration class AMDCPUConfig < CPUConfig + CPUFREQ_DIR = '/sys/devices/system/cpu/cpufreq' + BOOST_PATH = "#{CPUFREQ_DIR}/boost" + SCALING_GOVERNOR_GLOB = '/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor' + TURBO_DISABLED_VALUE = '0' + TURBO_ENABLED_VALUE = '1' + PERFORMANCE_GOVERNOR = 'performance' + private def disable_turbo_boost # sudo requires the flag '-S' in order to take input from stdin - BenchmarkRunner.check_call("sudo -S sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") - at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo 1 > /sys/devices/system/cpu/cpufreq/boost'", quiet: true) } + BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'") + at_exit { BenchmarkRunner.check_call("sudo -S sh -c 'echo #{TURBO_ENABLED_VALUE} > #{BOOST_PATH}'", quiet: true) } end def maximize_frequency @@ -109,26 +122,26 @@ def maximize_frequency end def turbo_disabled? - @turbo_disabled ||= File.exist?('/sys/devices/system/cpu/cpufreq/boost') && - File.read('/sys/devices/system/cpu/cpufreq/boost').strip == '0' + @turbo_disabled ||= File.exist?(BOOST_PATH) && + File.read(BOOST_PATH).strip == TURBO_DISABLED_VALUE end def frequency_maximized? - @frequency_maximized ||= Dir.glob('/sys/devices/system/cpu/cpu*/cpufreq/scaling_governor').all? do |governor| - File.read(governor).strip == 'performance' + @frequency_maximized ||= Dir.glob(SCALING_GOVERNOR_GLOB).all? do |governor| + File.read(governor).strip == PERFORMANCE_GOVERNOR end end def check_pstate(turbo:) unless turbo || turbo_disabled? puts("You forgot to disable boost:") - puts(" sudo sh -c 'echo 0 > /sys/devices/system/cpu/cpufreq/boost'") + puts(" sudo sh -c 'echo #{TURBO_DISABLED_VALUE} > #{BOOST_PATH}'") exit(-1) end unless frequency_maximized? puts("You forgot to set the performance governor:") - puts(" sudo cpupower frequency-set -g performance") + puts(" sudo cpupower frequency-set -g #{PERFORMANCE_GOVERNOR}") exit(-1) end end