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
2 changes: 1 addition & 1 deletion docs/denoise.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ It adjusts the system settings to reduce interference by:

- raising the process priority to minimize the impact of the process scheduler
- using CPU core shielding to minimize interference from other processes
- requesting the system to avoid tubo-boosting to keep the CPU at a constant frequency
- requesting the system to avoid turbo-boosting to keep the CPU at a constant frequency
- requesting the system to use a performance governor, which tries to keep the CPU at a high frequency
- reducing the frequency with which perf events can be collected, except when in profiling mode

Expand Down
94 changes: 72 additions & 22 deletions rebench/denoise.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@
import sys

from argparse import ArgumentParser
from glob import glob
from math import log, floor
from multiprocessing import Pool
from os.path import isfile, join as path_join, abspath, dirname, realpath
from subprocess import check_output, CalledProcessError, DEVNULL, STDOUT
from typing import Optional, Union, Literal, TypedDict, TYPE_CHECKING
from typing import (
Literal,
Optional,
Tuple,
TypedDict,
Union,
TYPE_CHECKING,
)

if TYPE_CHECKING:
from typing import NotRequired

denoise_py = os.path.abspath(__file__)
denoise_py = abspath(__file__)

if __name__ == "__main__":
# ensure that the rebench module is available
rebench_module = os.path.dirname(denoise_py)
sys.path.append(os.path.dirname(rebench_module))
rebench_module = dirname(denoise_py)
sys.path.append(dirname(rebench_module))

# pylint: disable-next=import-error
from output import output_as_str, UIError # type: ignore
Expand Down Expand Up @@ -65,7 +74,7 @@ def __init__(self):

def get_which(self):
if not self._which_path:
if os.path.isfile("/usr/bin/which"):
if isfile("/usr/bin/which"):
self._which_path = "/usr/bin/which"
else:
raise UIError(
Expand All @@ -87,7 +96,7 @@ def absolute_path_for_command(self, command, arguments_for_successful_exe):
selected_cmd = output_as_str(
check_output([self.get_which(), command], shell=False, stderr=DEVNULL)
).strip()
result_cmd = os.path.realpath(selected_cmd)
result_cmd = realpath(selected_cmd)
except CalledProcessError:
result_cmd = command

Expand Down Expand Up @@ -115,7 +124,7 @@ def ensure_denoise(self):
if self._denoise_path is None:
if os.access(denoise_py, os.X_OK):
self._denoise_path = denoise_py
elif not os.path.isfile(denoise_py):
elif not isfile(denoise_py):
raise UIError(
f"{denoise_py} not found. "
"Could it be that the user has no access to the file? "
Expand Down Expand Up @@ -269,32 +278,73 @@ def _set_scaling_governor(governor, num_cores) -> str:
return governor


_INTEL_NO_TURBO = "/sys/devices/system/cpu/intel_pstate/no_turbo"
_CPUFREQ_BOOST = "/sys/devices/system/cpu/cpufreq/boost"
_CPUFREQ_POLICY_DIR = "/sys/devices/system/cpu/cpufreq"


def _find_turbo_boost_control() -> Union[Tuple[list[str], bool], str]:
"""
Find the paths that allow us to disable turbo boost.
"""
if isfile(_INTEL_NO_TURBO):
return [_INTEL_NO_TURBO], True

if isfile(_CPUFREQ_BOOST):
return [_CPUFREQ_BOOST], False

# e.g. amd-pstate in active mode only provides per-policy boost files
policy_boost_files = sorted(
glob(path_join(_CPUFREQ_POLICY_DIR, "policy*", "boost"))
)
if policy_boost_files:
return policy_boost_files, False

return (
"failed: No turbo boost control found. Checked "
+ _INTEL_NO_TURBO
+ ", "
+ _CPUFREQ_BOOST
+ ", and "
+ path_join(_CPUFREQ_POLICY_DIR, "policy*", "boost")
)


def _read_no_turbo() -> Optional[Union[bool, str]]:
control = _find_turbo_boost_control()
if isinstance(control, str):
return control

files, indicates_disabled = control
try:
with open(
"/sys/devices/system/cpu/intel_pstate/no_turbo", "r", encoding="utf-8"
) as nt_file:
return nt_file.read().strip() == "1"
values = []
for filename in files:
with open(filename, "r", encoding="utf-8") as nt_file:
values.append(nt_file.read().strip() == "1")
except IOError as e:
if e.errno == 2 and "No such file" in str(e):
return "failed: No such file: /sys/devices/system/cpu/intel_pstate/no_turbo"
return "failed: " + str(e)

if indicates_disabled:
return all(values)
return not any(values)


def _set_no_turbo(no_turbo_value: bool) -> Union[Literal[True], str]:
if no_turbo_value:
value = "1"
control = _find_turbo_boost_control()
if isinstance(control, str):
return control

files, indicates_disabled = control
if indicates_disabled:
value = "1" if no_turbo_value else "0"
else:
value = "0"
value = "0" if no_turbo_value else "1"

try:
with open(
"/sys/devices/system/cpu/intel_pstate/no_turbo", "w", encoding="utf-8"
) as nt_file:
nt_file.write(value + "\n")
for filename in files:
with open(filename, "w", encoding="utf-8") as nt_file:
nt_file.write(value + "\n")
except IOError as e:
if e.errno == 2 and "No such file" in str(e):
return "failed: No such file: /sys/devices/system/cpu/intel_pstate/no_turbo"
return "failed: " + str(e)

return True
Expand Down
35 changes: 30 additions & 5 deletions rebench/tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,24 @@ def test_rebench(rebench_cmd, rebench_conf, rebench_module_path, test_data_file)
"can_set_scaling_governor",
"can_minimize_perf_sampling",
],
"cassius": ["can_set_nice", "can_set_scaling_governor"],
"laertes": ["can_set_nice", "can_set_scaling_governor"],
"ophelia": ["can_set_nice", "can_set_scaling_governor"],
"cassius": [
"can_set_nice",
"can_set_scaling_governor",
"initial_no_turbo",
"can_minimize_perf_sampling",
],
"laertes": [
"can_set_nice",
"can_set_scaling_governor",
"initial_no_turbo",
"can_minimize_perf_sampling",
],
"ophelia": [
"can_set_nice",
"can_set_scaling_governor",
"initial_no_turbo",
"can_minimize_perf_sampling",
],
"zullie1": ["can_set_nice"],
}
_default_capabilities = [
Expand Down Expand Up @@ -111,11 +126,21 @@ def test_machine_denoise_capabilities():
assert isinstance(
capabilities, dict
), f"Expected capabilities to be a dict, but got {capabilities}"

bool_expectations = {}
for k, v in capabilities.items():
if v is True:
bool_expectations[k] = v

for cap in expectations:
assert cap in capabilities, (
assert cap in bool_expectations, (
f"Expected {cap} to be in capabilities, but got {capabilities},"
f" got_json: {got_json}, raw_output: {raw_output} on {hostname}"
)
assert (
capabilities[cap] is True
bool_expectations[cap] is True
), f"Expected {cap} to be True, but got {capabilities} on {hostname}"
del bool_expectations[cap]
assert (
not bool_expectations
), f"Expected no more capabilities, but got {bool_expectations}"