Files
stack/tests/perf/test_meter.py
kert cb530c25d9 chore: snapshot WIP + fix cross-test conf pollution
Bundled commit across unrelated threads: perf collector/meter/logs/export
updates, SSO bootstrap and traefik/nginx/gitea infra, dev backend scripts
(gitea/github/woodpecker), diag test updates, and misc config.

Also fix test pollution: five tests were doing raw sys.modules.pop("conf")
to simulate ImportError on `import conf`, but raw dict mutation isn't
tracked by monkeypatch and leaked the eviction into subsequent tests.
Downstream tests that did `from conf import secret` at module level held
references to the pre-eviction conf, while re-imports inside tests got a
fresh conf, so patches to conf.cfg._data didn't land on the secret()
closure's cfg — causing tests/conf/test_conf.py::TestSecret and
tests/conf/test_connect.py::TestDuckdb::test_custom_db_name to fail.

Fix: use monkeypatch.delitem(sys.modules, "conf", raising=False) so the
eviction is reverted on teardown. Applied to test_diag_ci.py, test_init.py,
test_tracer.py, and test_resource.py (two sites).
2026-04-10 13:09:14 -04:00

171 lines
5.6 KiB
Python

"""Tests for real OTel MeterProvider setup and metric instruments."""
from __future__ import annotations
import pytest
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import InMemoryMetricReader
@pytest.fixture()
def meter_setup():
"""Create a fresh MeterProvider + reader pair (no global mutation)."""
reader = InMemoryMetricReader()
provider = MeterProvider(metric_readers=[reader])
yield provider, reader
provider.shutdown()
class TestRealMetrics:
"""Verify metric instrument creation and recording."""
def test_counter(self, meter_setup):
provider, reader = meter_setup
meter = provider.get_meter("test.pipeline")
counter = meter.create_counter(
"stack_cache_hits_total",
description="Cache hits",
)
counter.add(1, {"pipeline": "readmissions"})
counter.add(3, {"pipeline": "readmissions"})
data = reader.get_metrics_data()
rm = data.resource_metrics
assert len(rm) > 0
sm = rm[0].scope_metrics
assert len(sm) > 0
metric = sm[0].metrics[0]
assert metric.name == "stack_cache_hits_total"
dp = metric.data.data_points[0]
assert dp.value == 4
def test_histogram(self, meter_setup):
provider, reader = meter_setup
meter = provider.get_meter("test.pipeline")
hist = meter.create_histogram(
"stack_step_duration_seconds",
unit="s",
description="Step duration",
)
hist.record(0.5, {"pipeline": "core", "step": "encounter"})
hist.record(1.2, {"pipeline": "core", "step": "encounter"})
data = reader.get_metrics_data()
metric = data.resource_metrics[0].scope_metrics[0].metrics[0]
assert metric.name == "stack_step_duration_seconds"
dp = metric.data.data_points[0]
assert dp.count == 2
assert dp.sum == pytest.approx(1.7)
def test_gauge_via_up_down_counter(self, meter_setup):
provider, reader = meter_setup
meter = provider.get_meter("test.pipeline")
gauge = meter.create_up_down_counter(
"stack_step_rows_out",
description="Rows output by step",
)
gauge.add(100, {"step": "core.encounter"})
data = reader.get_metrics_data()
metric = data.resource_metrics[0].scope_metrics[0].metrics[0]
assert metric.name == "stack_step_rows_out"
def test_multiple_instruments(self, meter_setup):
provider, reader = meter_setup
meter = provider.get_meter("test.pipeline")
counter = meter.create_counter("test_counter_a")
hist = meter.create_histogram("test_hist_a")
counter.add(5)
hist.record(2.0)
data = reader.get_metrics_data()
names = {
m.name
for rm in data.resource_metrics
for sm in rm.scope_metrics
for m in sm.metrics
}
assert "test_counter_a" in names
assert "test_hist_a" in names
class TestCreateMeter:
"""Verify create_meter returns MockMeter or real meter."""
def test_returns_mock_when_disabled(self, monkeypatch):
monkeypatch.setenv("STACK_TELEMETRY", "false")
import perf._meter as mod
mod._provider_ready = False
m = mod.create_meter("test")
from perf._meter import MockMeter
assert isinstance(m, MockMeter)
def test_mock_instrument_methods_are_noops(self):
from perf._meter import MockMeter, _MockInstrument
instr = _MockInstrument()
instr.add(1.0, {"k": "v"})
instr.record(2.5)
instr.set(3.0, {"k": "v"})
meter = MockMeter()
assert isinstance(meter.create_counter("c"), _MockInstrument)
assert isinstance(meter.create_histogram("h"), _MockInstrument)
assert isinstance(meter.create_up_down_counter("g"), _MockInstrument)
assert isinstance(meter.create_observable_gauge("og"), _MockInstrument)
def test_returns_mock_when_enabled_but_provider_missing(self, monkeypatch):
# Provider not yet set up — falls into the second MockMeter return.
monkeypatch.setenv("STACK_TELEMETRY", "true")
import perf._meter as mod
from perf._meter import MockMeter
mod._provider_ready = False
assert isinstance(mod.create_meter("test"), MockMeter)
def test_returns_real_when_provider_ready(self, monkeypatch):
import perf._meter as mod
from perf._meter import MockMeter
mod._provider_ready = True
try:
m = mod.create_meter("test.scope")
assert not isinstance(m, MockMeter)
finally:
mod._provider_ready = False
class TestSetupMeterProvider:
"""setup_meter_provider() configures the global MeterProvider."""
def test_sets_provider_ready(self):
import perf._meter as mod
original_ready = mod._provider_ready
try:
mod._provider_ready = False
mod.setup_meter_provider()
assert mod._provider_ready is True
finally:
mod._provider_ready = original_ready
class TestShutdownMeterProvider:
"""shutdown_meter_provider() flushes and resets state."""
def test_noop_when_not_ready(self):
import perf._meter as mod
mod._provider_ready = False
mod.shutdown_meter_provider() # should return without error
assert mod._provider_ready is False
def test_resets_provider_ready(self, monkeypatch):
import perf._meter as mod
mod._provider_ready = True
mod.shutdown_meter_provider()
assert mod._provider_ready is False