use super::catalog::{ALL_METRICS, STATS_ONLY_METRICS};
use super::operator_docs_annotations::all_annotations;
pub const COMMITTED_DOC_REL: &str = "docs/reports/flight-metrics-reference.md";
pub const WEBSITE_DOC_REL: &str =
"website/src/content/docs/agents-using/flight-metrics-reference.md";
const WEBSITE_FRONT_MATTER: &str = "\
---
title: Flight metrics reference
description: Operator reference for every cqlite.* metric in CQLite's observability catalog — which names are live OTel instruments and which are stats-only, readable solely from Database::stats().
sidebar:
label: Flight metrics reference
order: 20
---
";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetricKind {
Counter,
Gauge,
Histogram,
}
impl MetricKind {
pub fn label(self) -> &'static str {
match self {
MetricKind::Counter => "counter",
MetricKind::Gauge => "gauge",
MetricKind::Histogram => "histogram",
}
}
}
#[derive(Debug, Clone)]
pub struct MetricDoc {
pub name: &'static str,
pub kind: MetricKind,
pub unit: &'static str,
pub summary: &'static str,
pub attributes: &'static [&'static str],
pub interpretation: &'static str,
pub round_item: &'static str,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DocGenError {
MissingAnnotation(&'static str),
UnknownMetric(&'static str),
DuplicateAnnotation(&'static str),
}
impl std::fmt::Display for DocGenError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DocGenError::MissingAnnotation(m) => write!(
f,
"metric `{m}` is catalogued but has no operator annotation in \
operator_docs::ANNOTATIONS — no undocumented instrument may ship (#2426)"
),
DocGenError::UnknownMetric(m) => write!(
f,
"operator annotation names `{m}`, which is not in catalog::ALL_METRICS \
(stale annotation)"
),
DocGenError::DuplicateAnnotation(m) => {
write!(f, "operator annotation for `{m}` appears more than once")
}
}
}
}
impl std::error::Error for DocGenError {}
pub fn operator_metric_docs() -> Result<Vec<MetricDoc>, DocGenError> {
let mut seen = std::collections::HashSet::new();
for a in all_annotations() {
if !seen.insert(a.name) {
return Err(DocGenError::DuplicateAnnotation(a.name));
}
if !ALL_METRICS.contains(&a.name) {
return Err(DocGenError::UnknownMetric(a.name));
}
}
let mut out = Vec::with_capacity(ALL_METRICS.len());
for name in ALL_METRICS {
match all_annotations().find(|a| a.name == *name) {
Some(a) => out.push(a.clone()),
None => return Err(DocGenError::MissingAnnotation(name)),
}
}
Ok(out)
}
fn render_attributes(d: &MetricDoc) -> String {
if d.attributes.is_empty() {
"_(none)_".to_string()
} else {
d.attributes
.iter()
.map(|a| format!("`{a}`"))
.collect::<Vec<_>>()
.join("<br>")
}
}
pub fn render_markdown() -> Result<String, DocGenError> {
let mut docs = operator_metric_docs()?;
docs.sort_by(|a, b| a.name.cmp(b.name));
let stats_only: std::collections::HashSet<&str> =
STATS_ONLY_METRICS.iter().map(|m| m.name).collect();
let (stats_only_docs, live): (Vec<MetricDoc>, Vec<MetricDoc>) = docs
.iter()
.cloned()
.partition(|d| stats_only.contains(d.name));
let mut s = String::new();
let mut line = |t: &str| {
s.push_str(t);
s.push('\n');
};
line("# CQLite Flight metrics — operator reference");
line("");
line(
"> GENERATED from the observability catalog \
(`cqlite-core/src/observability/catalog.rs` + `operator_docs.rs`) by \
`cargo run -p cqlite-core --example gen_operator_metrics_doc`. \
Do NOT edit by hand — edit the catalog/annotations and regenerate. \
The `operator-metrics-doc` agent-gate component fails if this file drifts \
from the catalog (issue #2426).",
);
line("");
line(
"Operator-facing reference for every `cqlite.*` metric name in CQLite's \
observability catalog, covering Arrow Flight and the storage/write/compaction \
paths. Names, units, and bounded attribute sets are generated from the code.",
);
line("");
line(
"**Two populations.** Most catalogued names are LIVE OTel instruments: \
registered with the meter, and scrapeable once they have recorded a value. \
The rest are **stats-only** — no instrument is ever registered, so they are \
never on a scrape at all; read them from the in-process \
`Database::stats().memory_stats` snapshot. The two are listed in separate \
sections below.",
);
line("");
line("Related: the Flight/Trino operator docs (`docs/flight-trino/`) and the round scoreboard template (issue #2399) link back to the entries here.");
line("");
line(&format!(
"Catalogued metrics: **{}** — **{}** live OTel instruments and **{}** \
stats-only (not scrapeable).",
docs.len(),
live.len(),
stats_only_docs.len(),
));
line("");
line("## Live instruments");
line("");
line(
"Registered with the OTel meter; each appears on a Prometheus scrape / OTel \
collector export once it has recorded a value.",
);
line("");
line("| Metric | Type | Unit | Attributes | Operator meaning | Healthy vs alarming |");
line("|---|---|---|---|---|---|");
for d in &live {
line(&format!(
"| `{}` | {} | `{}` | {} | {} | {} |",
d.name,
d.kind.label(),
d.unit,
render_attributes(d),
d.summary,
d.interpretation,
));
}
if live.is_empty() {
line("| _(none)_ | | | | | |");
}
line("");
line("## Stats-only metrics — NOT OTel instruments");
line("");
line(&format!(
"These **{}** catalogued names have NO OTel instrument: nothing registers them \
with a meter, so a Prometheus scrape or OTel collector will never show them. \
Read them from the in-process `Database::stats()` snapshot at the field named \
in the last column. This is enforced, not documentation: \
`catalog::STATS_ONLY_METRICS` is what this section is generated from, and the \
`stats_only_metrics_are_catalogued_and_never_otel_registered` guard fails the \
build if one of them ever does get an instrument (issue #1705).",
stats_only_docs.len(),
));
line("");
line("| Metric | Type | Unit | Attributes | Operator meaning | Healthy vs alarming | Read it from |");
line("|---|---|---|---|---|---|---|");
for d in &stats_only_docs {
let field = STATS_ONLY_METRICS
.iter()
.find(|m| m.name == d.name)
.map(|m| format!("`Database::stats().{}`", m.stats_field))
.unwrap_or_else(|| "—".to_string());
line(&format!(
"| `{}` | {} | `{}` | {} | {} | {} | {} |",
d.name,
d.kind.label(),
d.unit,
render_attributes(d),
d.summary,
d.interpretation,
field,
));
}
if stats_only_docs.is_empty() {
line("| _(none)_ | | | | | | |");
}
line("");
line("## Round-scoreboard mapping (issue #2399)");
line("");
line("Metrics a #2399 round-template scoreboard item consumes. Round handoffs (#2367-style) link the item to its metric here instead of re-explaining it.");
line("");
line("| Metric | Scoreboard item |");
line("|---|---|");
let mut any = false;
for d in &docs {
if d.round_item != "—" {
any = true;
line(&format!("| `{}` | {} |", d.name, d.round_item));
}
}
if !any {
line("| _(none)_ | _(none)_ |");
}
line("");
Ok(s)
}
pub fn render_website_markdown() -> Result<String, DocGenError> {
Ok(format!("{WEBSITE_FRONT_MATTER}{}", render_markdown()?))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_catalogued_metric_has_an_operator_annotation() {
let docs = operator_metric_docs().expect("every ALL_METRICS entry must be annotated");
assert_eq!(
docs.len(),
ALL_METRICS.len(),
"operator doc count must equal the catalog metric count"
);
}
#[test]
fn no_annotation_names_a_metric_absent_from_the_catalog() {
for a in all_annotations() {
assert!(
ALL_METRICS.contains(&a.name),
"annotation names `{}`, absent from ALL_METRICS",
a.name
);
}
}
#[test]
fn attributes_are_catalogued_bounded_keys() {
let docs = operator_metric_docs().unwrap();
for d in &docs {
for a in d.attributes {
assert!(
a.starts_with("cqlite."),
"attribute `{a}` on `{}` must be a namespaced bounded key",
d.name
);
}
}
}
#[test]
fn render_is_deterministic_and_non_empty() {
let a = render_markdown().unwrap();
let b = render_markdown().unwrap();
assert_eq!(a, b, "render must be byte-stable across runs");
assert!(a.contains("# CQLite Flight metrics — operator reference"));
for name in ALL_METRICS {
assert!(a.contains(name), "rendered doc must mention `{name}`");
}
}
#[test]
fn rendered_counts_and_sections_are_derived_from_the_catalog() {
let doc = render_markdown().expect("render must succeed");
let stats_only: std::collections::HashSet<&str> =
STATS_ONLY_METRICS.iter().map(|m| m.name).collect();
let total = ALL_METRICS.len();
let n_stats_only = ALL_METRICS
.iter()
.filter(|n| stats_only.contains(*n))
.count();
let n_live = total - n_stats_only;
let expected_counts = format!(
"Catalogued metrics: **{total}** — **{n_live}** live OTel instruments and \
**{n_stats_only}** stats-only (not scrapeable)."
);
assert!(
doc.contains(&expected_counts),
"the rendered counts must be derived from the catalog; expected \
{expected_counts:?}"
);
assert!(
!doc.contains("## All instruments"),
"a single 'All instruments' section misrepresents the stats-only names"
);
assert!(
!doc.contains("Total instruments:"),
"a single instrument total counts stats-only names as instruments"
);
assert!(
doc.contains("once it has recorded a value"),
"the live-instrument section must condition a scrape appearance on the \
instrument having recorded"
);
assert!(
!doc.contains("so these appear on a Prometheus scrape"),
"an unconditional 'these appear on a scrape' claim is false for a \
registered instrument that has never recorded"
);
let live_head = "## Live instruments";
let stats_head = "## Stats-only metrics — NOT OTel instruments";
let live_at = doc.find(live_head).expect("live-instrument section");
let stats_at = doc.find(stats_head).expect("stats-only section");
assert!(live_at < stats_at, "sections must render in a stable order");
let live_section = &doc[live_at..stats_at];
let stats_end = doc[stats_at..]
.find("\n## ")
.map(|o| stats_at + o)
.unwrap_or(doc.len());
let stats_section = &doc[stats_at..stats_end];
for name in ALL_METRICS {
let row = format!("| `{name}` |");
if stats_only.contains(name) {
assert!(
stats_section.contains(&row),
"stats-only `{name}` must be listed in the stats-only section"
);
assert!(
!live_section.contains(&row),
"`{name}` has no OTel instrument, so it must NOT be listed as a \
live instrument"
);
} else {
assert!(
live_section.contains(&row),
"`{name}` is a live instrument and must be listed as one"
);
assert!(
!stats_section.contains(&row),
"`{name}` has a live instrument, so it must NOT be listed as \
stats-only"
);
}
}
for m in STATS_ONLY_METRICS {
assert!(
stats_section.contains(&format!("`Database::stats().{}`", m.stats_field)),
"the stats-only row for `{}` must name its stats field",
m.name
);
}
}
#[test]
fn committed_operator_metrics_doc_is_fresh() {
let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("cqlite-core has a repo-root parent");
let path = repo_root.join(COMMITTED_DOC_REL);
let committed = std::fs::read_to_string(&path).unwrap_or_else(|e| {
panic!(
"committed operator metrics doc {} is missing ({e}); regenerate with \
`cargo run -p cqlite-core --example gen_operator_metrics_doc`",
path.display()
)
});
let fresh = render_markdown().expect("render must succeed");
assert_eq!(
committed,
fresh,
"{} is STALE vs the catalog; regenerate with \
`cargo run -p cqlite-core --example gen_operator_metrics_doc`",
path.display()
);
let web_path = repo_root.join(WEBSITE_DOC_REL);
let web_committed = std::fs::read_to_string(&web_path).unwrap_or_else(|e| {
panic!(
"published website metrics page {} is missing ({e}); regenerate with \
`cargo run -p cqlite-core --example gen_operator_metrics_doc`",
web_path.display()
)
});
let web_fresh = render_website_markdown().expect("website render must succeed");
assert_eq!(
web_committed,
web_fresh,
"{} is STALE vs the catalog; regenerate with \
`cargo run -p cqlite-core --example gen_operator_metrics_doc`",
web_path.display()
);
}
#[test]
fn missing_annotation_is_detected() {
let e = DocGenError::MissingAnnotation("cqlite.example.unannotated");
assert!(e.to_string().contains("no operator annotation"));
let u = DocGenError::UnknownMetric("cqlite.example.stale");
assert!(u.to_string().contains("not in catalog::ALL_METRICS"));
}
}