subms_otel/lib.rs
1//! `subms-otel` - OpenTelemetry bridge for the [`subms`] perf harness.
2//!
3//! Wires the harness's [`SubMsObserver`] hook + the post-bench
4//! [`SubMsBenchSummary`] + the [`SubMsTimer`] timeline into OpenTelemetry
5//! Histogram / Span emission, without the harness itself ever pulling in
6//! the OTEL dependency tree.
7//!
8//! # Feature flags
9//!
10//! Capability features:
11//!
12//! - `bridge` (default) - always-available bridge: post-hoc helpers,
13//! [`CompositeObserver`], counter + gauge surface constants, the
14//! reference-drift recorder.
15//! - `observer` - both [`OtelObserver`] (sync) and [`OtelObserverAsync`]
16//! (hand-rolled SPSC ring; target overhead < 300 ns per `on_record`).
17//! - `exemplars` - per-bucket tail-debug exemplar reservoir
18//! ([`ExemplarReservoir`]). Configurable K; default K = 5.
19//! - `tracing` - span emission per stage record + W3C TraceContext parent
20//! inheritance ([`TracingObserver`]).
21//!
22//! Bootstrap:
23//!
24//! - `autoconfig` - env-driven one-line SDK wiring + runtime observer registry.
25//!
26//! Exporter helpers (independent; pick what you ship):
27//!
28//! - `exporter-otlp` - [`OtlpBuilder`] + [`ExporterOtlpHelper`].
29//! - `exporter-prometheus` - [`PrometheusBuilder`] + [`ExporterPrometheusHelper`].
30//! - `exporter-stdout` - [`ExporterStdoutHelper`].
31//!
32//! Both observers honour the same semantic-conventions table:
33//!
34//! | Attribute key | Source |
35//! |---------------------------|-------------------------------------------------------|
36//! | `subms.workload` | `ctx.workload` / `summary.workload` |
37//! | `subms.lang` | `ctx.lang` / `summary.lang` |
38//! | `subms.stage` | `ctx.stage` / stage name |
39//! | `subms.stage.kind` | `ctx.stage_kind.as_str()` |
40//! | `subms.recipe.slug` | `meta["subms.recipe.slug"]` |
41//! | `subms.recipe.category` | `meta["subms.recipe.category"]` |
42//! | `subms.workload.feature` | `meta["subms.workload.feature"]` |
43//! | `subms.workload.entries` | `inputs["entries"]` |
44//! | `subms.workload.seed` | `inputs["seed"]` |
45//! | `subms.host` | `meta["host"]` |
46//! | `subms.hardware.tier` | `meta["hardware_tier"]` |
47//! | `subms.crate.version` | `meta["crate_version"]` |
48//!
49//! Inputs and meta arrive only via `on_summarize` (the harness's
50//! [`ObservationCtx`] is intentionally minimal). The sync observer therefore
51//! emits with just `workload`, `lang`, `stage`, and `stage.kind` per record;
52//! `on_summarize` re-emits the whole histogram bucket set under the fuller
53//! attribute set. The async observer caches the latest summary's inputs+meta
54//! between drain passes and applies them to drained samples once available.
55//!
56//! [`SubMsObserver`]: subms::SubMsObserver
57//! [`SubMsBenchSummary`]: subms::SubMsBenchSummary
58//! [`SubMsTimer`]: subms::SubMsTimer
59//! [`ObservationCtx`]: subms::ObservationCtx
60
61// Public re-exports of the harness types consumers will need.
62pub use subms::{
63 ObservationCtx, SubMsBenchSummary, SubMsObserver, SubMsStageKind, SubMsStageSummary,
64 SubMsTimer, SubMsTimerCheckpoint,
65};
66
67#[cfg(feature = "autoconfig")]
68mod autoconfig;
69#[cfg(feature = "bridge")]
70mod bridge;
71#[cfg(feature = "bridge")]
72mod composite;
73#[cfg(feature = "bridge")]
74mod drift;
75#[cfg(feature = "exemplars")]
76mod exemplars;
77#[cfg(feature = "exporter-otlp")]
78mod exporter_otlp;
79#[cfg(feature = "exporter-prometheus")]
80mod exporter_prometheus;
81#[cfg(feature = "exporter-stdout")]
82mod exporter_stdout;
83#[cfg(feature = "observer")]
84mod observer;
85#[cfg(feature = "observer")]
86mod observer_async;
87#[cfg(feature = "bridge")]
88mod resource;
89#[cfg(feature = "tracing")]
90mod tracing_observer;
91
92#[cfg(feature = "autoconfig")]
93pub use autoconfig::{
94 SubMsObserverRegistration, SubMsOtelAutoConfig, auto_configure, clear_registered_observers,
95 register_observer, registered_observers,
96};
97#[cfg(feature = "bridge")]
98pub use bridge::{
99 HISTOGRAM_NAME, HISTOGRAM_UNIT, attributes_from_ctx, attributes_from_summary, export_summary,
100 export_timer, histogram_boundaries,
101};
102#[cfg(feature = "bridge")]
103pub use composite::CompositeObserver;
104#[cfg(feature = "bridge")]
105pub use drift::{
106 REFERENCE_DIVERGENCE_COUNTER_NAME, ReferenceDivergenceRecorder, divergence_attributes,
107 record_reference_divergence,
108};
109#[cfg(feature = "exemplars")]
110pub use exemplars::{DEFAULT_RESERVOIR_K, EXEMPLAR_GAUGE_NAME, Exemplar, ExemplarReservoir};
111#[cfg(feature = "exporter-otlp")]
112pub use exporter_otlp::{ExporterOtlpHelper, OtlpBuilder, OtlpProtocol};
113#[cfg(feature = "exporter-prometheus")]
114pub use exporter_prometheus::{
115 ExporterPrometheusHelper, PrometheusBuilder, PrometheusTextExporter,
116};
117#[cfg(feature = "exporter-stdout")]
118pub use exporter_stdout::ExporterStdoutHelper;
119#[cfg(feature = "observer")]
120pub use observer::OtelObserver;
121#[cfg(feature = "observer")]
122pub use observer_async::OtelObserverAsync;
123#[cfg(feature = "bridge")]
124pub use resource::SubMsOtelResource;
125#[cfg(feature = "tracing")]
126pub use tracing_observer::{TRACING_SPAN_NAME, TracingObserver};
127
128/// Counter name incremented on every `on_record` regardless of which observer
129/// surface received the sample.
130#[cfg(feature = "bridge")]
131pub const OPS_TOTAL_COUNTER_NAME: &str = "subms.bench.ops_total";
132
133/// ObservableGauge name published by [`OtelObserverAsync`] tracking the
134/// current channel depth.
135#[cfg(feature = "bridge")]
136pub const IN_FLIGHT_GAUGE_NAME: &str = "subms.bench.in_flight";
137
138/// Counter name incremented when a sample is retained by an
139/// [`ExemplarReservoir`].
140#[cfg(feature = "bridge")]
141pub const EXEMPLARS_KEPT_COUNTER_NAME: &str = "subms.otel.exemplars_kept_total";
142
143/// Counter name for back-pressure-dropped samples. Promoted from the async
144/// observer's private constant so dashboards can wire it directly.
145#[cfg(feature = "bridge")]
146pub const DROPPED_TOTAL_COUNTER_NAME: &str = "subms.otel.dropped_total";