Skip to content
Open
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
10 changes: 9 additions & 1 deletion nodescraper/plugins/inband/amdsmi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@
# SOFTWARE.
#
###############################################################################
from .amdsmi_flavor_collector import AmdSmiFlavorCollector
from .amdsmi_flavor_plugin import AmdSmiFlavorPlugin
from .amdsmi_plugin import AmdSmiPlugin
from .amdsmidata_flavor import AmdSmiFlavorDataModel

__all__ = ["AmdSmiPlugin"]
__all__ = [
"AmdSmiPlugin",
"AmdSmiFlavorPlugin",
"AmdSmiFlavorCollector",
"AmdSmiFlavorDataModel",
]
579 changes: 579 additions & 0 deletions nodescraper/plugins/inband/amdsmi/amdsmi_flavor_collector.py

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions nodescraper/plugins/inband/amdsmi/amdsmi_flavor_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from .amdsmi_flavor_collector import AmdSmiFlavorCollector
from .amdsmi_plugin import AmdSmiPlugin
from .amdsmidata_flavor import AmdSmiFlavorDataModel


class AmdSmiFlavorPlugin(AmdSmiPlugin):
"""amd-smi plugin variant with driver-flavor collection.

Detects the loaded driver flavor and issues flavor-appropriate amd-smi commands:
- mxGPU host driver (gim on Linux, amdgpuv on ESXi) -> HostDriver* models
- guest amdgpu on a virtual function inside a VM -> Guest* models
- bare-metal amdgpu on physical hardware -> base AmdSmi* models
Data is built into AmdSmiFlavorDataModel. The base AmdSmiPlugin (guest/bare-metal
only) is left unchanged for callers that do not need host/VF support.
"""

DATA_MODEL = AmdSmiFlavorDataModel # type: ignore[assignment]
COLLECTOR = AmdSmiFlavorCollector
61 changes: 61 additions & 0 deletions nodescraper/plugins/inband/amdsmi/amdsmidata_flavor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""amd-smi data model that widens the base (guest/bare-metal only) ``AmdSmiDataModel``
field types to also accept the mxGPU host-driver and guest-VF model variants. The
collector picks the concrete model per driver flavor; this model only relaxes the
annotations so those instances are accepted and serialized. The base ``AmdSmiDataModel``
and its consumers are unchanged.
"""

from __future__ import annotations

from typing import Optional, Union

from pydantic import Field

from nodescraper.plugins.inband.amdsmi.amdsmidata import (
AmdSmiDataModel,
AmdSmiListItem,
AmdSmiMetric,
AmdSmiStatic,
AmdSmiVersion,
BadPages,
Topo,
XgmiLinks,
XgmiMetrics,
)

from .amdsmidata_guest import GuestAmdSmiMetric, GuestAmdSmiStatic
from .amdsmidata_host import (
HostDriverAmdSmiListItem,
HostDriverAmdSmiMetric,
HostDriverAmdSmiStatic,
HostDriverAmdSmiVersion,
HostDriverBadPages,
HostDriverTopo,
HostDriverXgmiLinks,
HostDriverXgmiMetrics,
)


class AmdSmiFlavorDataModel(AmdSmiDataModel):
"""AmdSmiDataModel with host/guest driver-flavor model variants allowed."""

# Base (guest/bare-metal) type is listed first; the collector builds the right
# concrete instance per flavor, so widening the annotation is sufficient.
# Widening the base annotations is an intentional LSP override (invariant list),
# so the type: ignore[assignment] markers below are expected.
version: Optional[Union[AmdSmiVersion, HostDriverAmdSmiVersion]] = None # type: ignore[assignment]
gpu_list: Optional[list[Union[AmdSmiListItem, HostDriverAmdSmiListItem]]] = Field( # type: ignore[assignment]
default_factory=list
)
topology: Optional[list[Union[Topo, HostDriverTopo]]] = Field(default_factory=list) # type: ignore[assignment]
bad_pages: Optional[list[Union[BadPages, HostDriverBadPages]]] = Field(default_factory=list) # type: ignore[assignment]
static: Optional[list[Union[AmdSmiStatic, GuestAmdSmiStatic, HostDriverAmdSmiStatic]]] = Field( # type: ignore[assignment]
default_factory=list
)
metric: Optional[list[Union[AmdSmiMetric, GuestAmdSmiMetric, HostDriverAmdSmiMetric]]] = Field( # type: ignore[assignment]
default_factory=list
)
xgmi_metric: Optional[list[Union[XgmiMetrics, HostDriverXgmiMetrics]]] = Field( # type: ignore[assignment]
default_factory=list
)
xgmi_link: Optional[list[Union[XgmiLinks, HostDriverXgmiLinks]]] = Field(default_factory=list) # type: ignore[assignment]
73 changes: 73 additions & 0 deletions nodescraper/plugins/inband/amdsmi/amdsmidata_guest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Guest-VF (virtualized amdgpu) Pydantic models for amd-smi data.

A guest VM sees a single amdgpu virtual function (VF). Its amd-smi build shares
the bare-metal amdgpu JSON schema EXCEPT that a VF does not expose physical /
host-owned fields: fan control, SoC power state, power/thermal limits, NUMA
topology, XGMI power-down policy, and energy/throttle/voltage-curve/perf-level
counters. The bare-metal ``AmdSmi*`` models mark those fields as required, so a
guest payload fails to build against them; these subclasses relax exactly those
fields to optional and inherit all other structure unchanged.

amd-smi was not exercised on the guest before the host/guest driver-flavor work,
so the base ``AmdSmi*`` models had implicitly assumed bare-metal amdgpu.
"""

from __future__ import annotations

from typing import Optional, Union

from pydantic import ConfigDict

from nodescraper.plugins.inband.amdsmi.amdsmidata import (
AmdSmiMetric,
AmdSmiStatic,
MetricEnergy,
MetricFan,
MetricMemUsage,
MetricThrottle,
MetricVoltageCurve,
StaticBus,
StaticLimit,
StaticNuma,
StaticSocPstate,
StaticVbios,
StaticXgmiPlpd,
)


class GuestStaticBus(StaticBus):
"""Guest amd-smi (ROCm 7.14+) adds a per-link ``pcie_levels`` block to the bus
section that the base ``StaticBus`` (``extra="forbid"``) rejects. Ignore unknown
bus fields so a new field doesn't drop the whole static payload, matching the
host models' schema-drift resilience."""

model_config = ConfigDict(extra="ignore")


class GuestAmdSmiStatic(AmdSmiStatic):
"""Bare-metal static schema minus the physical fields a guest VF omits."""

# Base bus (StaticBus) forbids extras; the guest build adds pcie_levels.
bus: GuestStaticBus # type: ignore[assignment]
soc_pstate: Optional[StaticSocPstate] = None
xgmi_plpd: Optional[StaticXgmiPlpd] = None
numa: Optional[StaticNuma] = None # type: ignore[assignment]
limit: Optional[StaticLimit] = None
# A guest VF's static payload carries an ``ifwi`` firmware block (name,
# build_date, part_number, version) that the bare-metal schema omits; its
# shape matches StaticVbios.
ifwi: Optional[StaticVbios] = None


class GuestAmdSmiMetric(AmdSmiMetric):
"""Bare-metal metric schema minus the physical fields a guest VF omits."""

# These physical fields are required on the bare-metal base model; a guest VF
# omits them, so relax to Optional (intentional LSP-widening override).
fan: Optional[MetricFan] = None # type: ignore[assignment]
voltage_curve: Optional[MetricVoltageCurve] = None
perf_level: Optional[Union[str, dict]] = None
xgmi_err: Optional[Union[str, dict]] = None
energy: Optional[MetricEnergy] = None
throttle: Optional[MetricThrottle] = None # type: ignore[assignment]
mem_usage: Optional[MetricMemUsage] = None # type: ignore[assignment]
Loading
Loading