Add `perf` as the 12th skinny package with full OpenTelemetry instrumentation for pipeline traces, Prometheus metrics, and Loki-correlated logs. - src/perf/: TracerProvider, MeterProvider, LoggerProvider bridge, PipelineCollector, psutil system metrics, FileExporter fallback, FastAPI middleware, auto-file Gitea issues on step/test failure - infra/otel/: OTel Collector fan-out (traces→Jaeger, metrics→Prometheus, logs→Loki), all services migrated from jaeger:4317 to collector - infra/grafana/dashboards/: 8-panel pipeline performance dashboard - Pipeline runner auto-instruments all 189 steps with zero-cost MockTracer/MockSpan no-ops when telemetry is disabled - 82 new tests, 12,055 total passing Closes #203, closes #204, closes #205, closes #206, closes #207, closes #208, closes #209, closes #210, closes #211, closes #212, closes #213
104 lines
3.4 KiB
Python
104 lines
3.4 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)
|