use std::fmt::Write as _;
use nasaga_backend::SagaStoreMetrics;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SagaOperationalMetrics {
pub started_total: u64,
pub completed_total: u64,
pub compensated_total: u64,
pub manual_intervention_total: u64,
pub manually_closed_total: u64,
pub quota_rejections_total: u64,
pub action_rate_rejections_total: u64,
pub unknown_result_total: u64,
pub retry_attempt_total: u64,
pub conflict_total: u64,
pub running_current: u64,
pub waiting_resolution_current: u64,
pub compensating_current: u64,
pub manual_intervention_current: u64,
pub due_timer_current: u64,
pub lifecycle_duration_count: u64,
pub lifecycle_duration_micros_sum: u64,
pub lifecycle_quantiles: Vec<nasaga_backend::SagaLifecycleQuantiles>,
pub kafka_result_processing_total: u64,
pub kafka_result_processing_micros_sum: u64,
pub kafka_result_retry_total: u64,
pub kafka_result_dlt_requested_total: u64,
pub kafka_result_ack_total: u64,
pub kafka_result_duplicate_total: u64,
pub kafka_command_processing_total: u64,
pub kafka_command_processing_micros_sum: u64,
pub kafka_command_retry_total: u64,
pub kafka_command_dlt_requested_total: u64,
pub kafka_command_ack_total: u64,
pub kafka_command_duplicate_total: u64,
}
impl SagaOperationalMetrics {
pub fn render_prometheus(&self) -> String {
let mut output = String::with_capacity(2_048);
counter(&mut output, "nasaga_started_total", self.started_total);
counter(&mut output, "nasaga_completed_total", self.completed_total);
counter(
&mut output,
"nasaga_compensated_total",
self.compensated_total,
);
counter(
&mut output,
"nasaga_manual_intervention_total",
self.manual_intervention_total,
);
counter(
&mut output,
"nasaga_manually_closed_total",
self.manually_closed_total,
);
counter(
&mut output,
"nasaga_quota_rejections_total",
self.quota_rejections_total,
);
counter(
&mut output,
"nasaga_action_rate_rejections_total",
self.action_rate_rejections_total,
);
counter(
&mut output,
"nasaga_unknown_result_total",
self.unknown_result_total,
);
counter(
&mut output,
"nasaga_retry_attempt_total",
self.retry_attempt_total,
);
counter(&mut output, "nasaga_conflict_total", self.conflict_total);
gauge(&mut output, "nasaga_running", self.running_current);
gauge(
&mut output,
"nasaga_waiting_resolution",
self.waiting_resolution_current,
);
gauge(
&mut output,
"nasaga_compensating",
self.compensating_current,
);
gauge(
&mut output,
"nasaga_manual_intervention",
self.manual_intervention_current,
);
gauge(&mut output, "nasaga_due_timer", self.due_timer_current);
summary(
&mut output,
"nasaga_lifecycle_duration_seconds",
self.lifecycle_duration_count,
self.lifecycle_duration_micros_sum,
);
for quantiles in &self.lifecycle_quantiles {
labeled_gauge(
&mut output,
"nasaga_lifecycle_duration_seconds",
&quantiles.workflow,
quantiles.definition_version,
"0.5",
quantiles.p50_micros,
);
labeled_gauge(
&mut output,
"nasaga_lifecycle_duration_seconds",
&quantiles.workflow,
quantiles.definition_version,
"0.95",
quantiles.p95_micros,
);
labeled_gauge(
&mut output,
"nasaga_lifecycle_duration_seconds",
&quantiles.workflow,
quantiles.definition_version,
"0.99",
quantiles.p99_micros,
);
}
summary(
&mut output,
"nasaga_kafka_result_processing_duration_seconds",
self.kafka_result_processing_total,
self.kafka_result_processing_micros_sum,
);
counter(
&mut output,
"nasaga_kafka_result_retry_total",
self.kafka_result_retry_total,
);
counter(
&mut output,
"nasaga_kafka_result_dlt_requested_total",
self.kafka_result_dlt_requested_total,
);
counter(
&mut output,
"nasaga_kafka_result_ack_total",
self.kafka_result_ack_total,
);
counter(
&mut output,
"nasaga_kafka_result_duplicate_total",
self.kafka_result_duplicate_total,
);
summary(
&mut output,
"nasaga_kafka_command_processing_duration_seconds",
self.kafka_command_processing_total,
self.kafka_command_processing_micros_sum,
);
counter(
&mut output,
"nasaga_kafka_command_retry_total",
self.kafka_command_retry_total,
);
counter(
&mut output,
"nasaga_kafka_command_dlt_requested_total",
self.kafka_command_dlt_requested_total,
);
counter(
&mut output,
"nasaga_kafka_command_ack_total",
self.kafka_command_ack_total,
);
counter(
&mut output,
"nasaga_kafka_command_duplicate_total",
self.kafka_command_duplicate_total,
);
output.push_str(&crate::render_saga_latency_metrics());
output
}
}
impl From<SagaStoreMetrics> for SagaOperationalMetrics {
fn from(store: SagaStoreMetrics) -> Self {
let process = crate::process_metrics_snapshot();
Self {
started_total: store.started_total,
completed_total: store.completed_total,
compensated_total: store.compensated_total,
manual_intervention_total: store.manual_intervention_total,
manually_closed_total: store.manually_closed_total,
quota_rejections_total: process.quota_rejections_total,
action_rate_rejections_total: process.action_rate_rejections_total,
unknown_result_total: store.unknown_result_total,
retry_attempt_total: store.retry_attempt_total,
conflict_total: store.conflict_total,
running_current: store.running_current,
waiting_resolution_current: store.waiting_resolution_current,
compensating_current: store.compensating_current,
manual_intervention_current: store.manual_intervention_current,
due_timer_current: store.due_timer_current,
lifecycle_duration_count: store.lifecycle_duration_count,
lifecycle_duration_micros_sum: store.lifecycle_duration_micros_sum,
lifecycle_quantiles: store.lifecycle_quantiles,
kafka_result_processing_total: process.kafka_result_processing_total,
kafka_result_processing_micros_sum: process.kafka_result_processing_micros_sum,
kafka_result_retry_total: process.kafka_result_retry_total,
kafka_result_dlt_requested_total: process.kafka_result_dlt_requested_total,
kafka_result_ack_total: process.kafka_result_ack_total,
kafka_result_duplicate_total: process.kafka_result_duplicate_total,
kafka_command_processing_total: process.kafka_command_processing_total,
kafka_command_processing_micros_sum: process.kafka_command_processing_micros_sum,
kafka_command_retry_total: process.kafka_command_retry_total,
kafka_command_dlt_requested_total: process.kafka_command_dlt_requested_total,
kafka_command_ack_total: process.kafka_command_ack_total,
kafka_command_duplicate_total: process.kafka_command_duplicate_total,
}
}
}
fn labeled_gauge(
output: &mut String,
name: &str,
workflow: &str,
version: u32,
quantile: &str,
micros: u64,
) {
let workflow = workflow.replace('\\', "\\\\").replace('"', "\\\"");
let _ = writeln!(
output,
"{name}{{workflow=\"{workflow}\",definition_version=\"{version}\",quantile=\"{quantile}\"}} {}",
micros as f64 / 1_000_000.0
);
}
pub(crate) fn record_quota_rejection() {
crate::record_quota_rejection();
}
pub(crate) fn record_action_rate_rejection() {
crate::record_action_rate_rejection();
}
fn counter(output: &mut String, name: &str, value: u64) {
let _ = writeln!(output, "# TYPE {name} counter\n{name} {value}");
}
fn gauge(output: &mut String, name: &str, value: u64) {
let _ = writeln!(output, "# TYPE {name} gauge\n{name} {value}");
}
fn summary(output: &mut String, name: &str, count: u64, micros_sum: u64) {
let seconds_sum = micros_sum as f64 / 1_000_000.0;
let _ = writeln!(
output,
"# TYPE {name} summary\n{name}_count {count}\n{name}_sum {seconds_sum:.6}"
);
}