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).
171 lines
5.6 KiB
Python
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
|