from __future__ import annotations
import struct
import pytest
from modelexpress import nic_metrics
from modelexpress.nic_metrics import (
NicUtilizationSampler,
SourceLoadSampler,
_parse_rate_bytes_per_sec,
make_source_load_provider,
)
def test_parse_rate():
assert _parse_rate_bytes_per_sec("400 Gb/sec (4X HDR)") == 400e9 / 8
assert _parse_rate_bytes_per_sec("100 Gb/sec (4X EDR)") == 100e9 / 8
assert _parse_rate_bytes_per_sec("garbage") is None
class _FakeCounters:
def __init__(self, xmit_words, rcv_words):
self.xmit = xmit_words
self.rcv = rcv_words
def __call__(self, path):
if path.endswith("port_xmit_data"):
return self.xmit
if path.endswith("port_rcv_data"):
return self.rcv
return None
class _Clock:
def __init__(self):
self.t = 0.0
def __call__(self):
return self.t
def _sampler(counters, clock, link_bps=400e9 / 8):
return NicUtilizationSampler(
"mlx5_0",
_reader=counters,
_clock=clock,
_link_bytes_per_sec=link_bps,
)
def test_first_sample_is_none_baseline():
s = _sampler(_FakeCounters(0, 0), _Clock())
assert s.sample() is None
def test_utilization_from_delta():
counters = _FakeCounters(0, 0)
clock = _Clock()
s = _sampler(counters, clock)
assert s.sample() is None link_bps = 400e9 / 8 bytes_in_1s = link_bps * 0.5 counters.xmit = int(bytes_in_1s / 4) counters.rcv = 0
clock.t = 1.0
assert abs(s.sample() - 0.5) < 1e-6
def test_uses_busier_direction():
counters = _FakeCounters(0, 0)
clock = _Clock()
s = _sampler(counters, clock)
s.sample()
link_bps = 400e9 / 8
counters.xmit = int((link_bps * 0.2) / 4) counters.rcv = int((link_bps * 0.7) / 4) clock.t = 1.0
assert abs(s.sample() - 0.7) < 1e-6
def test_clamped_to_one():
counters = _FakeCounters(0, 0)
clock = _Clock()
s = _sampler(counters, clock)
s.sample()
counters.xmit = 10**18 clock.t = 1.0
assert s.sample() == 1.0
def test_counter_reset_does_not_go_negative():
counters = _FakeCounters(10**9, 10**9)
clock = _Clock()
s = _sampler(counters, clock)
s.sample()
counters.xmit = 0 counters.rcv = 0
clock.t = 1.0
assert s.sample() == 0.0
def test_no_device_returns_none():
s = NicUtilizationSampler(None)
assert s.sample() is None
def test_unreadable_counters_return_none():
s = _sampler(lambda path: None, _Clock())
assert s.sample() is None
def test_missing_link_rate_returns_none():
s = NicUtilizationSampler(
"mlx5_0", _reader=_FakeCounters(0, 0), _clock=_Clock(), _link_bytes_per_sec=None
)
assert s.sample() is None
def test_source_load_sampler_uses_affine_device():
calls = []
def fake_factory(dev):
calls.append(dev)
return type("S", (), {"sample": lambda self: 0.4})()
s = SourceLoadSampler(
0,
_resolver=lambda did: "mlx5_0",
_lister=lambda: ["mlx5_0", "mlx5_1", "mlx5_2"],
_sampler_factory=fake_factory,
)
assert calls == ["mlx5_0"]
assert s.sample() == 0.4
def test_source_load_sampler_falls_back_to_busiest_node_nic():
loads = {"mlx5_0": 0.1, "mlx5_1": 0.9, "mlx5_2": 0.3}
def fake_factory(dev):
return type("S", (), {"sample": lambda self, d=dev: loads[d]})()
s = SourceLoadSampler(
0,
_resolver=lambda did: None, _lister=lambda: list(loads),
_sampler_factory=fake_factory,
)
assert abs(s.sample() - 0.9) < 1e-9
def test_source_load_sampler_no_nic_returns_none():
s = SourceLoadSampler(
0,
_resolver=lambda did: None,
_lister=lambda: [],
_sampler_factory=lambda d: None,
)
assert s.sample() is None
def test_make_source_load_provider_returns_callable():
provider = make_source_load_provider(0)
val = provider()
assert val is None or isinstance(val, float)
assert val is None or 0.0 <= val <= 1.0
class TestNetlinkVfFallback:
"""SR-IOV VF pods have no `counters/` sysfs; the reader falls back to netlink."""
@staticmethod
def _no_sysfs(_path):
return None
def test_falls_back_to_netlink_when_sysfs_counters_absent(self):
counters = {"rdma_tx_bytes": 0, "rdma_rx_bytes": 0}
def nl(_dev, _port):
return dict(counters)
clock = iter([0.0, 1.0])
s = nic_metrics.NicUtilizationSampler(
"mlx5_0",
_reader=self._no_sysfs,
_clock=lambda: next(clock),
_link_bytes_per_sec=100.0,
_nl_counters=nl,
)
assert s.sample() is None counters["rdma_tx_bytes"] = 50 assert s.sample() == pytest.approx(0.5)
def test_netlink_bytes_are_not_scaled_by_the_sysfs_word_size(self):
"""sysfs counters are in 4-octet words; netlink values are already bytes."""
counters = {"rdma_tx_bytes": 0, "rdma_rx_bytes": 0}
clock = iter([0.0, 1.0])
s = nic_metrics.NicUtilizationSampler(
"mlx5_0",
_reader=self._no_sysfs,
_clock=lambda: next(clock),
_link_bytes_per_sec=1000.0,
_nl_counters=lambda _d, _p: dict(counters),
)
s.sample()
counters["rdma_rx_bytes"] = 1000
assert s.sample() == pytest.approx(1.0)
def test_default_hwcounter_set_without_byte_totals_yields_none(self):
"""The real VF case: netlink works, but byte counters are not enabled."""
default_set = {"rx_write_requests": 12345, "rx_read_requests": 678, "out_of_buffer": 0}
clock = iter([0.0, 1.0, 2.0])
s = nic_metrics.NicUtilizationSampler(
"mlx5_0",
_reader=self._no_sysfs,
_clock=lambda: next(clock),
_link_bytes_per_sec=100.0,
_nl_counters=lambda _d, _p: dict(default_set),
)
assert s.sample() is None
assert s.sample() is None
def test_netlink_failure_degrades_to_zero(self):
def boom(_dev, _port):
raise OSError("no netlink in this sandbox")
s = nic_metrics.NicUtilizationSampler(
"mlx5_0",
_reader=self._no_sysfs,
_link_bytes_per_sec=100.0,
_nl_counters=boom,
)
with pytest.raises(OSError):
s._read_bytes_netlink()
def test_sysfs_still_wins_when_present(self):
"""On a PF host the sysfs path must not be displaced by the fallback."""
def nl(_dev, _port):
raise AssertionError("netlink must not be consulted when sysfs works")
clock = iter([0.0, 1.0])
s = nic_metrics.NicUtilizationSampler(
"mlx5_0",
_reader=lambda p: 25 if p.endswith("port_xmit_data") else 0,
_clock=lambda: next(clock),
_link_bytes_per_sec=100.0,
_nl_counters=nl,
)
assert s.sample() is None
assert s.sample() == 0.0
class TestNetlinkWireEncoding:
"""The attribute codec, pinned against values read from <rdma/rdma_netlink.h>."""
def test_message_types_match_uapi_header(self):
assert nic_metrics._NL_MSG_STAT_GET == 5137
assert nic_metrics._NL_MSG_DEV_GET == 5121
assert nic_metrics._NETLINK_RDMA == 20
def test_attr_roundtrip_with_padding(self):
blob = nic_metrics._nl_attr(nic_metrics._NLA_DEV_INDEX, struct.pack("=I", 7))
assert len(blob) % 4 == 0
parsed = nic_metrics._nl_parse_attrs(blob)
assert struct.unpack("=I", parsed[nic_metrics._NLA_DEV_INDEX][0][:4])[0] == 7
def test_parse_attrs_masks_nested_flag(self):
payload = b"\x00\x00\x00\x00"
blob = struct.pack("=HH", 8, nic_metrics._NLA_HWCOUNTERS | 0x8000) + payload
assert nic_metrics._NLA_HWCOUNTERS in nic_metrics._nl_parse_attrs(blob)
def test_parse_attrs_stops_on_truncated_attribute(self):
assert nic_metrics._nl_parse_attrs(struct.pack("=HH", 999, 1)) == {}
class TestNoReadingIsNotIdle:
"""None (no reading) and 0.0 (measured idle) are different answers."""
def test_source_load_sampler_ignores_nics_without_a_reading(self):
class _S:
def __init__(self, v): self._v = v
def sample(self): return self._v
s = nic_metrics.SourceLoadSampler(
0, _resolver=lambda _d: None, _lister=lambda: ["a", "b", "c"],
_sampler_factory=lambda d: _S({"a": None, "b": 0.4, "c": None}[d]),
)
assert s.sample() == pytest.approx(0.4)
def test_source_load_sampler_all_unknown_is_none(self):
class _S:
def sample(self): return None
s = nic_metrics.SourceLoadSampler(
0, _resolver=lambda _d: None, _lister=lambda: ["a", "b"],
_sampler_factory=lambda d: _S(),
)
assert s.sample() is None
def test_static_counters_are_a_measured_zero_not_unknown(self):
clock = iter([0.0, 1.0])
s = nic_metrics.NicUtilizationSampler(
"mlx5_0", _reader=lambda p: 100, _clock=lambda: next(clock),
_link_bytes_per_sec=1000.0,
)
assert s.sample() is None assert s.sample() == 0.0