blob: b4134b6954cabec9fd7b5e2aabe50419fdd62c99 [file] [log] [blame]
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""CPU Power related helpers."""
import contextlib
import errno
import logging
from pathlib import Path
from typing import Iterator, Optional
from chromite.lib import chromite_config
from chromite.lib import cros_build_lib
from chromite.lib import osutils
_AUTO_SET_GOV_CONTENT = (
"# Delete this file to turn off automatic performance governor switch.\n"
)
_CPU_PATH = Path("/sys/devices/system/cpu")
# cpupower command with default options used to set scaling governor for CPUs
_CPUPOWER_CMD = ["cpupower", "-c", "all", "frequency-set", "-g"]
def _FetchActiveCpuGovernor() -> Optional[str]:
"""Scans and returns the active governor if all cpus use it.
Returns:
The active governor or None if there is no single active governor.
"""
cpufreq = _CPU_PATH / "cpufreq"
governors = set()
offline_cpus = False
for x in cpufreq.glob("policy*/scaling_governor"):
try:
governors.add(x.read_text(encoding="utf-8"))
except OSError as e:
if e.errno == errno.EBUSY:
offline_cpus = True
continue
if offline_cpus:
logging.warning(
"Some CPUs are offline. Rebooting should bring them online."
)
if len(governors) != 1:
logging.warning(
"Too many active CPU governors; refusing to use 'performance'."
)
return None
return next(iter(governors))
def _AutoSetGovStickyConfigUpdate(perf_governor: bool, sticky: bool) -> None:
"""Create/remove config file based on performance governor sticky request.
Args:
perf_governor: If true, switch the CPU governors to performance.
sticky: If true, cache the switch request for subsequent runs.
"""
if not sticky:
return
if not perf_governor:
logging.info("Future runs will respect --autosetgov")
osutils.SafeUnlink(chromite_config.AUTO_SET_GOV_CONFIG)
elif not chromite_config.AUTO_SET_GOV_CONFIG.exists():
logging.info("Future runs will *always* use --autosetgov")
chromite_config.initialize()
chromite_config.AUTO_SET_GOV_CONFIG.write_text(
_AUTO_SET_GOV_CONTENT, encoding="utf-8"
)
@contextlib.contextmanager
def ModifyCpuGovernor(perf_governor: bool, sticky: bool) -> Iterator[None]:
"""A context manager to switch the CPU governor policy to performance.
This context manager will switch the CPU governors to the 'performance'
governor and will revert the policy to the default governor when it exits.
When the sticky option is selected, the governor will be automatically
switched to performance for the subsequent runs. To clear this automatic
switching, user must pass the perf_governor arg as false with sticky
argument set to true.
Args:
perf_governor: If true, switch the CPU governors to performance.
sticky: If true, cache the switch request for subsequent runs.
Yields:
Iterator.
"""
_AutoSetGovStickyConfigUpdate(perf_governor, sticky)
old_governor = None
new_governor = None
# If the config path is present we switch CPUs to performance governor.
if chromite_config.AUTO_SET_GOV_CONFIG.exists():
perf_governor = True
if perf_governor:
new_governor = "performance"
# Check if performance governor is supported by the CPU.
governor_path = (
_CPU_PATH / "cpu0" / "cpufreq" / "scaling_available_governors"
)
try:
governors = governor_path.read_text(encoding="utf-8").split()
except FileNotFoundError as e:
logging.debug("Error reading CPU scaling governor file: %s", e)
governors = []
try:
if "performance" in governors:
old_governor = _FetchActiveCpuGovernor()
# Switch the CPU governors to performance if they have the same
# active governor and if the active governor is not performance.
if new_governor and old_governor and new_governor != old_governor:
logging.info(
"Temporarily setting CPU governors to %s", new_governor
)
perf_cmd = _CPUPOWER_CMD + [new_governor]
try:
cros_build_lib.sudo_run(
perf_cmd, print_cmd=False, capture_output=True
)
except cros_build_lib.RunCommandError as e:
logging.warning("Error switching CPU governors: %s", e)
elif old_governor == "powersave":
logging.warning(
"Current CPU governor set to 'powersave' which can "
"slow down builds."
)
logging.warning(
"Use --autosetgov to automatically (and "
"temporarily) switch to 'performance'."
)
yield
finally:
if new_governor and old_governor and new_governor != old_governor:
logging.info("Restoring CPU governors to %s", old_governor)
restore_cmd = _CPUPOWER_CMD + [old_governor]
try:
cros_build_lib.sudo_run(
restore_cmd, print_cmd=False, capture_output=True
)
except cros_build_lib.RunCommandError as e:
logging.warning("Error restoring CPU governors: %s", e)