use introspection_core::StatusSnapshot;
use obs_core::{MetricsAggregator, export_otel_json_value, export_prometheus_text};
use crate::constants::DEFAULT_STATUS_REPORT_PATH;
use crate::helpers::{has_flag, option_value};
const OBS_EXPORT_API_VERSION: &str = "robotrt.obs.export.v1";
type SnapshotSource = super::snapshot_shared::SnapshotSource;
pub fn obs_export(args: &[String]) -> Result<(), String> {
let format = option_value(args, "--format").unwrap_or_else(|| "prometheus".to_string());
if format != "prometheus" && format != "otel" {
return Err(format!(
"unsupported --format value: {format} (expected prometheus|otel)"
));
}
let alert_template =
option_value(args, "--alert-template").unwrap_or_else(|| "none".to_string());
if alert_template != "none" && alert_template != "basic" {
return Err(format!(
"unsupported --alert-template value: {alert_template} (expected none|basic)"
));
}
let json = has_flag(args, "--json");
let (snapshot, source) = load_status_snapshot(args)?;
let aggregator = build_snapshot_aggregator(&snapshot);
let metrics_payload = if format == "prometheus" {
serde_json::Value::String(export_prometheus_text(&aggregator))
} else {
export_otel_json_value(&aggregator)
};
let template_payload = build_alert_template(&alert_template);
if json {
let payload = serde_json::json!({
"api_version": OBS_EXPORT_API_VERSION,
"kind": "obs_export",
"captured_at_unix_nanos": snapshot.captured_at_unix_nanos,
"source": source.json,
"query": {
"format": format,
"alert_template": alert_template,
},
"result": {
"metrics": metrics_payload,
"alert_template": template_payload,
}
});
println!(
"{}",
serde_json::to_string_pretty(&payload)
.map_err(|err| format!("serialize obs export payload failed: {err}"))?
);
} else {
println!("RobotRT Obs Export");
println!("source: {}", source.label);
println!("format: {}", format);
println!();
if format == "prometheus" {
println!(
"{}",
metrics_payload
.as_str()
.ok_or_else(|| String::from("unexpected prometheus payload type"))?
);
} else {
println!(
"{}",
serde_json::to_string_pretty(&metrics_payload)
.map_err(|err| format!("serialize otel payload failed: {err}"))?
);
}
if !template_payload.is_null() {
println!();
println!("alert_template:");
println!(
"{}",
template_payload
.as_str()
.ok_or_else(|| String::from("unexpected alert template payload type"))?
);
}
}
Ok(())
}
fn load_status_snapshot(
args: &[String],
) -> Result<(StatusSnapshot, SnapshotSource), String> {
super::snapshot_shared::load_status_snapshot(
args,
option_value(args, "--endpoint"),
"--timeout-ms",
DEFAULT_STATUS_REPORT_PATH,
)
}
fn build_snapshot_aggregator(snapshot: &StatusSnapshot) -> MetricsAggregator {
crate::commands::snapshot_shared::build_snapshot_aggregator(snapshot)
}
fn build_alert_template(kind: &str) -> serde_json::Value {
match kind {
"none" => serde_json::Value::Null,
"basic" => serde_json::Value::String(
"groups:\n - name: robotrt-basic\n rules:\n - alert: RobotRTUnhealthy\n expr: robotrt_health_overall > 0\n for: 2m\n labels:\n severity: critical\n annotations:\n summary: \"RobotRT health degraded or unhealthy\"\n\n - alert: RobotRTHighQueuePressure\n expr: robotrt_runtime_topics_pending_total > 100\n for: 3m\n labels:\n severity: warning\n annotations:\n summary: \"RobotRT topic queue pressure is high\""
.to_string(),
),
_ => serde_json::Value::Null,
}
}