From 976f35c3d18de7b6a8d4a3a831a22aad05270c3e Mon Sep 17 00:00:00 2001 From: Guillaume Fieni Date: Fri, 20 Sep 2024 11:09:59 +0200 Subject: [PATCH 1/4] build: Disable `too-many-positional-arguments` pylint warning --- .pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index c65f454..ac43722 100644 --- a/.pylintrc +++ b/.pylintrc @@ -435,7 +435,8 @@ disable=raw-checker-failed, too-many-locals, too-few-public-methods, too-many-instance-attributes, - line-too-long + line-too-long, + too-many-positional-arguments # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 53d4633c13a04919ec41e75939635178df957103 Mon Sep 17 00:00:00 2001 From: Guillaume Fieni Date: Fri, 20 Sep 2024 11:17:08 +0200 Subject: [PATCH 2/4] build: Move Ruff configuration to its own file --- .ruff.toml | 18 ++++++++++++++++++ pyproject.toml | 17 ----------------- 2 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..6a7d039 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,18 @@ +target-version = "py310" # should match minimal python version of PowerAPI + +[lint] +select = [ + "F", # pyflakes + "E", # pycodestyle errors + "W", # pycodestyle warnings + "B", # flake8-bugbear + "G", # flake8-logging-format + "PT", # flake8-pytest-style + "UP", # pyupgrade + "ERA", # eradicate + "RUF", # ruff +] + +ignore = [ + "E501", # line too long +] diff --git a/pyproject.toml b/pyproject.toml index 689ee7c..a1204e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,20 +55,3 @@ repository = "https://github.com/powerapi-ng/smartwatts-formula" [tool.setuptools.dynamic] version = {attr = "smartwatts.__version__"} - -[tool.ruff] -target-version = "py38" # should match minimal python version of PowerAPI -lint.select = [ - "F", # pyflakes - "E", # pycodestyle errors - "W", # pycodestyle warnings - "B", # flake8-bugbear - "G", # flake8-logging-format - "PT", # flake8-pytest-style - "UP", # pyupgrade - "ERA", # eradicate - "RUF", # ruff -] -lint.ignore = [ - "E501", # line too long -] From cc9fb3dd760e4794964ad96feaab11233c84c871 Mon Sep 17 00:00:00 2001 From: Guillaume Fieni Date: Fri, 20 Sep 2024 11:39:00 +0200 Subject: [PATCH 3/4] refactor: Use built-in collection types in type annotations --- src/smartwatts/__main__.py | 3 +-- src/smartwatts/actor/actor.py | 3 +-- src/smartwatts/actor/factory.py | 4 +--- src/smartwatts/handler/hwpc_report.py | 20 ++++++++++---------- src/smartwatts/model/cpu_topology.py | 4 +--- src/smartwatts/model/frequency_layer.py | 4 +--- src/smartwatts/model/power_model.py | 3 +-- src/smartwatts/model/sample_history.py | 3 +-- 8 files changed, 17 insertions(+), 27 deletions(-) diff --git a/src/smartwatts/__main__.py b/src/smartwatts/__main__.py index 3e8d1e2..9466f71 100644 --- a/src/smartwatts/__main__.py +++ b/src/smartwatts/__main__.py @@ -33,7 +33,6 @@ import signal import sys from collections import OrderedDict -from typing import Dict from powerapi import __version__ as powerapi_version from powerapi.backend_supervisor import BackendSupervisor @@ -90,7 +89,7 @@ def generate_smartwatts_parser() -> CommonCLIParsingManager: return pm -def generate_formula_configuration(config: Dict, cpu_topology: CPUTopology, scope: SmartWattsFormulaScope) -> SmartWattsFormulaConfig: +def generate_formula_configuration(config: dict, cpu_topology: CPUTopology, scope: SmartWattsFormulaScope) -> SmartWattsFormulaConfig: """ Generate a SmartWatts actor configuration. """ diff --git a/src/smartwatts/actor/actor.py b/src/smartwatts/actor/actor.py index 9607cae..2b3d715 100644 --- a/src/smartwatts/actor/actor.py +++ b/src/smartwatts/actor/actor.py @@ -31,7 +31,6 @@ import logging import re -from typing import Dict from powerapi.formula import FormulaActor, FormulaState from powerapi.handler import StartHandler, PoisonPillMessageHandler @@ -69,7 +68,7 @@ class SmartWattsFormulaActor(FormulaActor): This actor handle the reports for the SmartWatts formula. """ - def __init__(self, name, pushers: Dict[str, PusherActor], config: SmartWattsFormulaConfig, level_logger=logging.WARNING, timeout=None): + def __init__(self, name, pushers: dict[str, PusherActor], config: SmartWattsFormulaConfig, level_logger=logging.WARNING, timeout=None): super().__init__(name, pushers, level_logger, timeout) self.state = SmartWattsFormulaState(self, pushers, self.formula_metadata, config) diff --git a/src/smartwatts/actor/factory.py b/src/smartwatts/actor/factory.py index 34f49f3..293ce60 100644 --- a/src/smartwatts/actor/factory.py +++ b/src/smartwatts/actor/factory.py @@ -29,8 +29,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from typing import Dict - from powerapi.pusher import PusherActor from .actor import SmartWattsFormulaActor @@ -48,7 +46,7 @@ def __init__(self, actor_config: SmartWattsFormulaConfig): """ self.actor_config = actor_config - def __call__(self, name: str, pushers: Dict[str, PusherActor]) -> SmartWattsFormulaActor: + def __call__(self, name: str, pushers: dict[str, PusherActor]) -> SmartWattsFormulaActor: """ Create a new SmartWatts formula actor. :param name: Name of the actor diff --git a/src/smartwatts/handler/hwpc_report.py b/src/smartwatts/handler/hwpc_report.py index ad32fa1..9cf683c 100644 --- a/src/smartwatts/handler/hwpc_report.py +++ b/src/smartwatts/handler/hwpc_report.py @@ -33,7 +33,7 @@ import logging from collections import OrderedDict, defaultdict from math import ldexp, fabs -from typing import Dict, List, Any, Tuple +from typing import Any from powerapi.handler import Handler from powerapi.report import PowerReport, HWPCReport, FormulaReport @@ -50,7 +50,7 @@ class HwPCReportHandler(Handler): def __init__(self, state): Handler.__init__(self, state) self.layers = self._generate_frequency_layers() - self.ticks: OrderedDict[datetime.datetime, Dict[str, HWPCReport]] = OrderedDict() + self.ticks: OrderedDict[datetime.datetime, dict[str, HWPCReport]] = OrderedDict() def _generate_frequency_layers(self) -> OrderedDict[int, FrequencyLayer]: """ @@ -70,7 +70,7 @@ def _get_nearest_frequency_layer(self, frequency: int) -> FrequencyLayer: """ return self.layers.get(max(freq for freq in self.layers.keys() if freq <= frequency)) - def _compute_avg_pkg_frequency(self, system_msr: Dict[str, float]) -> int: + def _compute_avg_pkg_frequency(self, system_msr: dict[str, float]) -> int: """ Compute the average package frequency. :param system_msr: MSR events group of System target @@ -96,7 +96,7 @@ def handle(self, msg: HWPCReport) -> None: pusher.send_data(report) logging.debug('sent report: %s to %s', report, name) - def _process_oldest_tick(self) -> Tuple[List[PowerReport], List[FormulaReport]]: + def _process_oldest_tick(self) -> tuple[list[PowerReport], list[FormulaReport]]: """ Process the oldest tick stored in the stack and generate power reports for the running target(s). :return: Power reports of the running target(s) @@ -182,7 +182,7 @@ def _gen_formula_report(self, timestamp: datetime, pkg_frequency: int, layer: Fr } return FormulaReport(timestamp, self.state.sensor, layer.model.hash, metadata) - def _gen_power_report(self, timestamp: datetime, target: str, formula: str, power: float, ratio: float, metadata: Dict[str, Any]) -> PowerReport: + def _gen_power_report(self, timestamp: datetime, target: str, formula: str, power: float, ratio: float, metadata: dict[str, Any]) -> PowerReport: """ Generate a power report using the given parameters. :param timestamp: Timestamp of the measurements @@ -199,7 +199,7 @@ def _gen_power_report(self, timestamp: datetime, target: str, formula: str, powe } return PowerReport(timestamp, self.state.sensor, target, power, report_metadata) - def _gen_rapl_events_group(self, system_report) -> Dict[str, float]: + def _gen_rapl_events_group(self, system_report) -> dict[str, float]: """ Generate an events group with the RAPL reference event converted in Watts for the current socket. :param system_report: The HWPC report of the System target @@ -209,7 +209,7 @@ def _gen_rapl_events_group(self, system_report) -> Dict[str, float]: energy = ldexp(cpu_events[self.state.config.rapl_event], -32) / (self.state.config.reports_frequency / 1000) return {self.state.config.rapl_event: energy} - def _gen_msr_events_group(self, system_report) -> Dict[str, float]: + def _gen_msr_events_group(self, system_report) -> dict[str, float]: """ Generate an events group with the average of the MSR counters for the current socket. :param system_report: The HWPC report of the System target @@ -223,7 +223,7 @@ def _gen_msr_events_group(self, system_report) -> Dict[str, float]: msr_events_count[event_name] += 1 return {k: (v / msr_events_count[k]) for k, v in msr_events_group.items()} - def _gen_core_events_group(self, report) -> Dict[str, float]: + def _gen_core_events_group(self, report) -> dict[str, float]: """ Generate an events group with Core events for the current socket. The events value are the sum of the value for each CPU. @@ -237,7 +237,7 @@ def _gen_core_events_group(self, report) -> Dict[str, float]: return core_events_group - def _gen_agg_core_report_from_running_targets(self, targets_report) -> Dict[str, float]: + def _gen_agg_core_report_from_running_targets(self, targets_report) -> dict[str, float]: """ Generate an aggregate Core events group of the running targets for the current socket. :param targets_report: List of Core events group of the running targets @@ -251,7 +251,7 @@ def _gen_agg_core_report_from_running_targets(self, targets_report) -> Dict[str, return agg_core_events_group @staticmethod - def _extract_events_value(events: Dict[str, float]) -> List[float]: + def _extract_events_value(events: dict[str, float]) -> list[float]: """ Creates and return a list of events value from the events group. :param events: Events group diff --git a/src/smartwatts/model/cpu_topology.py b/src/smartwatts/model/cpu_topology.py index 9b82b32..d0572f8 100644 --- a/src/smartwatts/model/cpu_topology.py +++ b/src/smartwatts/model/cpu_topology.py @@ -29,8 +29,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from typing import List - class CPUTopology: """ @@ -73,7 +71,7 @@ def get_max_frequency(self) -> int: """ return self.freq_bclk * self.ratio_max - def get_supported_frequencies(self) -> List[int]: + def get_supported_frequencies(self) -> list[int]: """ Compute the supported frequencies for this CPU. :return: A list of supported frequencies in MHz diff --git a/src/smartwatts/model/frequency_layer.py b/src/smartwatts/model/frequency_layer.py index 7d1682d..e16d09d 100644 --- a/src/smartwatts/model/frequency_layer.py +++ b/src/smartwatts/model/frequency_layer.py @@ -29,8 +29,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from typing import List - from .sample_history import ReportHistory, ErrorHistory from .power_model import PowerModel @@ -60,7 +58,7 @@ def update_power_model(self, min_intercept: float, max_intercept: float) -> None self.model.learn_power_model(self.samples_history, min_intercept, max_intercept) self.error_history.clear() - def store_sample_in_history(self, power_reference: float, events_value: List[float]) -> None: + def store_sample_in_history(self, power_reference: float, events_value: list[float]) -> None: """ Append a sample to the history. :param power_reference: Power reference (RAPL) of the machine diff --git a/src/smartwatts/model/power_model.py b/src/smartwatts/model/power_model.py index 01cbcc2..5d145f8 100644 --- a/src/smartwatts/model/power_model.py +++ b/src/smartwatts/model/power_model.py @@ -32,7 +32,6 @@ import warnings from hashlib import sha1 from pickle import dumps -from typing import List, Optional from sklearn.linear_model import ElasticNet @@ -79,7 +78,7 @@ def learn_power_model(self, samples_history: ReportHistory, min_intercept: float self.hash = sha1(dumps(self.clf)).hexdigest() self.id += 1 - def predict_power_consumption(self, events: List[float]) -> Optional[float]: + def predict_power_consumption(self, events: list[float]) -> float | None: """ Compute a power estimation from the events value using the power model. :param events: Events value diff --git a/src/smartwatts/model/sample_history.py b/src/smartwatts/model/sample_history.py index e1c0303..d0d6a3b 100644 --- a/src/smartwatts/model/sample_history.py +++ b/src/smartwatts/model/sample_history.py @@ -31,7 +31,6 @@ from collections import deque from statistics import median, mean -from typing import List class ReportHistory: @@ -55,7 +54,7 @@ def __len__(self) -> int: """ return len(self.events_values) - def store_report(self, power_reference: float, events_value: List[float]) -> None: + def store_report(self, power_reference: float, events_value: list[float]) -> None: """ Append a report to the report's history. :param events_value: List of raw events value From f84c1c30a1668799b80779690d1dbb913f66ae9e Mon Sep 17 00:00:00 2001 From: Guillaume Fieni Date: Fri, 20 Sep 2024 11:49:12 +0200 Subject: [PATCH 4/4] build(pyproject): Update python minimal version and classifiers To match with PowerAPI v2.6.0 (powerapi-ng/powerapi#294) --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a1204e6..cd1aaa4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,12 +8,10 @@ description = "SmartWatts is a formula for a self-adaptive software-defined powe readme = "README.md" keywords = ["powerapi", "energy", "power-meter", "power-model", "green-computing", "containers"] license = {text = "BSD-3-Clause"} -requires-python = ">=3.8" +requires-python = ">=3.10" dynamic = ["version"] classifiers = [ - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12",