Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-metrics/src/main.rs
atlas 44984e0d36 move install_crypto_provider to swarm-secret-client
The rustls provider install is process setup every swarm binary runs, and
swarm-secret-client depends on no other swarm crate, so a binary that only
needs the provider and the store no longer links the queue client.

- swarm-secret-client/src/lib.rs, Cargo.toml: the function, its test, a
  `//!` line on why it lives there, and the rustls dependency; rustls
  `ring` as a dev-dependency feature, so the test builds both providers as
  the binaries do and fails if the install is dropped.
- swarm-queue-client/src/lib.rs, Cargo.toml: function and test removed;
  `connect()` calls the secret client's; rustls dropped (no other use),
  swarm-secret-client added.
- swarm-metrics-auth/Cargo.toml: drop swarm-queue-client, whose only use
  was the provider; the binary no longer links async-nats.
- swarm-logs-auth, swarm-logs, swarm-metrics, swarm-ci-metrics Cargo.toml:
  the dep comment no longer names the provider (they keep the dep for
  agent_token / the OIDC mint).
- Cargo.toml: the rustls comment names the provider's new home.
- 14 call sites (hive-agent, hive-c0re, hive-subagent-mcp x2,
  swarm-builder, swarm-ci-metrics, swarm-claude-login, swarm-controller,
  swarm-logs-auth, swarm-logs, swarm-metrics-auth, swarm-metrics,
  swarm-nats-auth, swarm-review-classifier): one-line path change.
- Cargo.lock: the dependency edges above.
2026-10-18 03:18:43 +02:00

247 lines
7.3 KiB
Rust

//! `swarm-metrics` — an agent's CLI for the swarm metrics store.
//!
//! The metrics-store sibling of `swarm-logs`: the same credential, the same
//! wrapper shape, and the store's own query language. Each verb is one GET
//! against the store's Prometheus-compatible read API through the
//! gateway, and prints the store's JSON answer unchanged for `jq`.
//!
//! **Read-only.** The verbs map onto `/api/v1/query`, `query_range`,
//! `series`, `labels` and `label/<name>/values`; nothing here writes.
//!
//! ⚠️ **The route applies no scoping.** Any authenticated caller reads the
//! whole swarm's metrics, every hive's.
mod auth;
mod query;
use anyhow::Result;
use clap::{Args, Parser, Subcommand};
use crate::query::{Request, Span};
#[derive(Parser)]
#[command(
name = "swarm-metrics",
version,
about = "query the swarm's metrics store from an agent"
)]
struct Cli {
#[command(subcommand)]
command: Verb,
}
/// Window and cap for the discovery verbs.
#[derive(Args)]
struct SpanArgs {
/// Start of the window: RFC 3339, a unix timestamp, or relative (`-1h`).
///
/// Unset means the store's default: the last day, from 00:00 UTC.
#[arg(long, allow_hyphen_values = true)]
start: Option<String>,
/// End of the window, in the same forms. Unset means now.
#[arg(long, allow_hyphen_values = true)]
end: Option<String>,
/// Return at most this many entries.
#[arg(long)]
limit: Option<u64>,
}
impl From<SpanArgs> for Span {
fn from(a: SpanArgs) -> Self {
Self {
start: a.start,
end: a.end,
limit: a.limit,
}
}
}
#[derive(Subcommand)]
enum Verb {
/// Evaluate a `MetricsQL` (`PromQL`) expression at one instant.
Query {
/// The expression. Quote it: `{`, `|` and `"` are the shell's too.
///
/// OTLP metric names keep their dots, so match them by
/// `{__name__="jobq.nodes"}` rather than as a bare name.
promql: String,
/// The instant: RFC 3339, a unix timestamp, or relative (`-5m`).
/// Unset means now.
#[arg(long, allow_hyphen_values = true)]
time: Option<String>,
},
/// Evaluate an expression over a window, one point per step.
QueryRange {
/// The expression, quoted as for `query`.
promql: String,
/// Start of the window: RFC 3339, a unix timestamp, or relative
/// (`-1h`). Required, so a range is always bounded.
#[arg(long, allow_hyphen_values = true)]
start: String,
/// End of the window, in the same forms. Unset means now.
#[arg(long, allow_hyphen_values = true)]
end: Option<String>,
/// Seconds or a duration (`1m`) between points. Unset means the
/// store's default.
#[arg(long)]
step: Option<String>,
},
/// List the series that match one or more selectors.
Series {
/// A series selector, such as `{"service.name"="nats"}`. Repeatable.
#[arg(required = true)]
matchers: Vec<String>,
#[command(flatten)]
span: SpanArgs,
},
/// List label names, optionally only those on matching series.
Labels {
/// A series selector to narrow to. Repeatable.
#[arg(long = "match")]
matchers: Vec<String>,
#[command(flatten)]
span: SpanArgs,
},
/// List one label's values; `__name__` lists every metric name.
LabelValues {
/// The label name.
name: String,
/// A series selector to narrow to. Repeatable.
#[arg(long = "match")]
matchers: Vec<String>,
#[command(flatten)]
span: SpanArgs,
},
/// Print this CLI's reference as markdown (source of `docs/tools/swarm-metrics-cli.md`).
#[command(hide = true)]
MarkdownDocs,
}
impl Verb {
/// The store request this verb makes; `None` for the local-only verb.
fn request(self) -> Option<Request> {
Some(match self {
Self::Query { promql, time } => Request::Query { promql, time },
Self::QueryRange {
promql,
start,
end,
step,
} => Request::QueryRange {
promql,
start,
end,
step,
},
Self::Series { matchers, span } => Request::Series {
matchers,
span: span.into(),
},
Self::Labels { matchers, span } => Request::Labels {
matchers,
span: span.into(),
},
Self::LabelValues {
name,
matchers,
span,
} => Request::LabelValues {
name,
matchers,
span: span.into(),
},
Self::MarkdownDocs => return None,
})
}
}
fn main() -> Result<()> {
let Some(request) = Cli::parse().command.request() else {
// `show_footer(false)`: same as swarm-logs, the footer is not our prose.
let options = clap_markdown::MarkdownOptions::new().show_footer(false);
print!("{}", clap_markdown::help_markdown_custom::<Cli>(&options));
return Ok(());
};
// Config read only for a store verb: `markdown-docs` runs in a nix check,
// where no credential exists.
swarm_secret_client::install_crypto_provider();
let cfg = auth::Config::from_env()?;
let stdout = std::io::stdout();
query::run(&cfg, &request, &mut stdout.lock())
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory as _;
fn parse(args: &[&str]) -> Option<Request> {
Cli::try_parse_from(std::iter::once("swarm-metrics").chain(args.iter().copied()))
.expect("parses")
.command
.request()
}
#[test]
fn the_command_tree_is_well_formed() {
Cli::command().debug_assert();
}
#[test]
fn a_range_query_without_a_start_is_refused() {
assert!(Cli::try_parse_from(["swarm-metrics", "query-range", "up"]).is_err());
}
#[test]
fn series_needs_a_selector() {
assert!(Cli::try_parse_from(["swarm-metrics", "series"]).is_err());
}
#[test]
fn label_values_maps_name_matchers_and_span() {
assert_eq!(
parse(&[
"label-values",
"__name__",
"--match",
"{service.name=\"nats\"}",
"--start",
"-1d",
"--limit",
"5",
]),
Some(Request::LabelValues {
name: "__name__".into(),
matchers: vec!["{service.name=\"nats\"}".into()],
span: Span {
start: Some("-1d".into()),
end: None,
limit: Some(5),
},
})
);
}
#[test]
fn query_range_maps_its_window() {
assert_eq!(
parse(&["query-range", "up", "--start", "-1h", "--step", "1m"]),
Some(Request::QueryRange {
promql: "up".into(),
start: "-1h".into(),
end: None,
step: Some("1m".into()),
})
);
}
#[test]
fn markdown_docs_makes_no_request() {
assert_eq!(parse(&["markdown-docs"]), None);
}
}