wire the authenticated OTLP push into vcs_metrics and hive-jobq-metrics
This commit is contained in:
parent
7913be5435
commit
361396cab8
6 changed files with 147 additions and 10 deletions
|
|
@ -23,13 +23,38 @@ use std::sync::{Arc, Mutex};
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use async_trait::async_trait;
|
||||
use hive_jobq_wire::StateCount;
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry::metrics::MeterProvider as _;
|
||||
use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig};
|
||||
use opentelemetry_http::{Bytes, HttpClient, HttpError, Request, Response};
|
||||
use opentelemetry_otlp::{MetricExporter, Protocol, WithExportConfig, WithHttpConfig};
|
||||
use opentelemetry_sdk::Resource;
|
||||
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
|
||||
|
||||
/// Type-erases a caller's concrete [`HttpClient`] impl so [`spawn_exporter`]
|
||||
/// can accept "any client, or none" without this crate depending on any one
|
||||
/// caller's concrete type (`swarm-controller`'s
|
||||
/// `swarm_queue_client::otel_http_client::AuthenticatedHttpClient` today,
|
||||
/// conceivably something else from a future caller) — the same genericity
|
||||
/// this crate already has over `N`/`R`, applied to the one other caller-
|
||||
/// supplied thing it touches.
|
||||
///
|
||||
/// A thin delegating newtype rather than a blanket `impl HttpClient for
|
||||
/// Box<dyn HttpClient>`: the orphan rule refuses that blanket impl outright
|
||||
/// (`Box` and `dyn HttpClient` are both foreign to this crate), so a local
|
||||
/// wrapper is the standard way around it, not a workaround chosen over a
|
||||
/// cleaner alternative.
|
||||
#[derive(Debug)]
|
||||
struct BoxedHttpClient(Box<dyn HttpClient>);
|
||||
|
||||
#[async_trait]
|
||||
impl HttpClient for BoxedHttpClient {
|
||||
async fn send_bytes(&self, request: Request<Bytes>) -> Result<Response<Bytes>, HttpError> {
|
||||
self.0.send_bytes(request).await
|
||||
}
|
||||
}
|
||||
|
||||
/// Default export cadence when `HYPERHIVE_OTEL_METRIC_INTERVAL_MS` is unset.
|
||||
/// Matches `hive-c0re::stats::otel_metrics`'s default — no reason for the
|
||||
/// exporters across this tree to disagree on how fresh "current" means by
|
||||
|
|
@ -53,9 +78,18 @@ const DEFAULT_INTERVAL: Duration = Duration::from_mins(1);
|
|||
/// Generic over `N`/`R` (the same parameters [`hive_jobq::scheduler::Scheduler`]
|
||||
/// itself takes) rather than any one host's node/resource vocabulary — the
|
||||
/// whole point of this crate is a shape any `hive-jobq` host can plug in.
|
||||
///
|
||||
/// `http_client`: `None` builds the exporter with its default (endpoint-
|
||||
/// only, no per-request auth) transport — the shape every caller used
|
||||
/// before this parameter existed. `Some(client)` routes every export
|
||||
/// through that [`HttpClient`] instead, for a caller whose destination
|
||||
/// checks a bearer token per request rather than a static header
|
||||
/// (`swarm-controller`'s case — see `swarm_queue_client::otel_http_client`'s
|
||||
/// module doc for why a static header doesn't fit an expiring token).
|
||||
pub fn spawn_exporter<N, R>(
|
||||
jobq: Arc<Mutex<hive_jobq::scheduler::Scheduler<N, R>>>,
|
||||
service_name: &str,
|
||||
http_client: Option<Box<dyn HttpClient>>,
|
||||
) -> Option<SdkMeterProvider>
|
||||
where
|
||||
N: Send + 'static,
|
||||
|
|
@ -83,7 +117,7 @@ where
|
|||
}
|
||||
});
|
||||
|
||||
match build_provider(interval, snapshot, service_name) {
|
||||
match build_provider(interval, snapshot, service_name, http_client) {
|
||||
Ok(provider) => {
|
||||
tracing::info!(%endpoint, ?interval, %service_name, "otel jobq-metrics: exporter enabled");
|
||||
Some(provider)
|
||||
|
|
@ -99,15 +133,22 @@ fn build_provider(
|
|||
interval: Duration,
|
||||
snapshot: Arc<Mutex<Vec<StateCount>>>,
|
||||
service_name: &str,
|
||||
http_client: Option<Box<dyn HttpClient>>,
|
||||
) -> Result<SdkMeterProvider> {
|
||||
// Same http/json, endpoint-from-env-only construction as
|
||||
// `hive-c0re::stats::otel_metrics` — see that module's doc comment for
|
||||
// why `with_endpoint` is deliberately never called here.
|
||||
let exporter = MetricExporter::builder()
|
||||
let mut builder = MetricExporter::builder()
|
||||
.with_http()
|
||||
.with_protocol(Protocol::HttpJson)
|
||||
.build()
|
||||
.context("build OTLP metric exporter")?;
|
||||
.with_protocol(Protocol::HttpJson);
|
||||
// Only when the caller supplied one — see `spawn_exporter`'s doc for
|
||||
// who does and why. Wrapped in `BoxedHttpClient` because
|
||||
// `with_http_client` wants an owned `T: HttpClient`, not the trait
|
||||
// object this parameter is typed as (see that newtype's own doc).
|
||||
if let Some(client) = http_client {
|
||||
builder = builder.with_http_client(BoxedHttpClient(client));
|
||||
}
|
||||
let exporter = builder.build().context("build OTLP metric exporter")?;
|
||||
let reader = PeriodicReader::builder(exporter)
|
||||
.with_interval(interval)
|
||||
.build();
|
||||
|
|
|
|||
Loading…
Reference in a new issue