diff --git a/nodescraper/plugins/inband/amdsmi/amdsmidata.py b/nodescraper/plugins/inband/amdsmi/amdsmidata.py index fd603028..3b8aae3c 100644 --- a/nodescraper/plugins/inband/amdsmi/amdsmidata.py +++ b/nodescraper/plugins/inband/amdsmi/amdsmidata.py @@ -523,6 +523,9 @@ class StaticCacheInfoItem(AmdSmiBaseModel): na_validator = field_validator("cache_size", mode="before")(na_to_none) +_STATIC_CLOCK_FREQ_LEVEL_VALIDATOR_FIELDS = tuple(f"Level_{i}" for i in range(16)) + + class StaticFrequencyLevels(AmdSmiBaseModel): """Static clock frequency levels; each level is normalized to ``ValueUnit``.""" @@ -534,8 +537,21 @@ class StaticFrequencyLevels(AmdSmiBaseModel): Level_0: ValueUnit = Field(..., alias="Level 0") Level_1: Optional[ValueUnit] = Field(default=None, alias="Level 1") Level_2: Optional[ValueUnit] = Field(default=None, alias="Level 2") - - _level_value_unit = field_validator("Level_0", "Level_1", "Level_2", mode="before")( + Level_3: Optional[ValueUnit] = Field(default=None, alias="Level 3") + Level_4: Optional[ValueUnit] = Field(default=None, alias="Level 4") + Level_5: Optional[ValueUnit] = Field(default=None, alias="Level 5") + Level_6: Optional[ValueUnit] = Field(default=None, alias="Level 6") + Level_7: Optional[ValueUnit] = Field(default=None, alias="Level 7") + Level_8: Optional[ValueUnit] = Field(default=None, alias="Level 8") + Level_9: Optional[ValueUnit] = Field(default=None, alias="Level 9") + Level_10: Optional[ValueUnit] = Field(default=None, alias="Level 10") + Level_11: Optional[ValueUnit] = Field(default=None, alias="Level 11") + Level_12: Optional[ValueUnit] = Field(default=None, alias="Level 12") + Level_13: Optional[ValueUnit] = Field(default=None, alias="Level 13") + Level_14: Optional[ValueUnit] = Field(default=None, alias="Level 14") + Level_15: Optional[ValueUnit] = Field(default=None, alias="Level 15") + + _level_value_unit = field_validator(*_STATIC_CLOCK_FREQ_LEVEL_VALIDATOR_FIELDS, mode="before")( coerce_value_unit_input ) diff --git a/test/unit/plugin/test_amdsmi_data.py b/test/unit/plugin/test_amdsmi_data.py index 9e28fbb9..f6c4f750 100644 --- a/test/unit/plugin/test_amdsmi_data.py +++ b/test/unit/plugin/test_amdsmi_data.py @@ -23,7 +23,7 @@ # SOFTWARE. # ############################################################################### -"""Unit tests for amd-smi pydantic models (ROCm 7.13 / legacy JSON shapes).""" +"""Unit tests for amd-smi pydantic models (legacy JSON, ROCm 7.2+ / AMD-SMI 26.2+).""" from typing import Any, Optional @@ -341,6 +341,36 @@ def test_static_frequency_levels_optional_levels(): assert levels.Level_2 is not None and levels.Level_2.value == 1300 +def test_static_frequency_levels_accepts_level_three_plus(): + """ROCm 7.2+ / AMD-SMI 26.2+ may expose additional DPM levels (e.g. Level 3).""" + levels = StaticFrequencyLevels.model_validate( + { + "Level 0": "400 MHz", + "Level 1": "800 MHz", + "Level 2": "1000 MHz", + "Level 3": "1143 MHz", + } + ) + assert levels.Level_3 is not None + assert levels.Level_3.value == 1143 + assert levels.Level_3.unit == "MHz" + + +def test_static_frequency_levels_legacy_amd_smi_three_levels_only(): + """Legacy static JSON: only Level 0–2 (no Level 3+ keys).""" + levels = StaticFrequencyLevels.model_validate( + { + "Level 0": {"value": 500, "unit": "MHz"}, + "Level 1": "900 MHz", + "Level 2": "1300 MHz", + } + ) + assert levels.Level_0.value == 500 + assert levels.Level_2 is not None and levels.Level_2.value == 1300 + assert levels.Level_3 is None + assert levels.Level_15 is None + + def test_static_limit_legacy_max_power(): """Legacy flat max_power field still resolves.""" limit = StaticLimit.model_validate(DUMMY_LIMIT_LEGACY)