use crate::topics::*;
use crate::types::*;
use anyhow::Result;
use mecha10_core::prelude::*;
use mecha10_core::topics::Topic;
pub struct SimulationCollector {
source: String,
backend: String,
running: bool,
restart_count: u64,
last_start_us: u64,
last_exit_reason: Option<String>,
}
impl SimulationCollector {
pub fn new(source: impl Into<String>, backend: impl Into<String>) -> Self {
Self {
source: source.into(),
backend: backend.into(),
running: false,
restart_count: 0,
last_start_us: 0,
last_exit_reason: None,
}
}
pub async fn record_start(&mut self, ctx: &Context) -> Result<()> {
let metrics = self.note_start();
self.publish_metrics(ctx, metrics).await
}
pub async fn record_exit(&mut self, ctx: &Context, reason: impl Into<String>) -> Result<()> {
let metrics = self.note_exit(reason);
self.publish_metrics(ctx, metrics).await
}
pub async fn publish(&self, ctx: &Context) -> Result<()> {
let metrics = self.snapshot();
self.publish_metrics(ctx, metrics).await
}
fn note_start(&mut self) -> SimulationConnectionMetrics {
self.running = true;
self.restart_count += 1;
self.last_start_us = crate::types::now_micros();
self.snapshot()
}
fn note_exit(&mut self, reason: impl Into<String>) -> SimulationConnectionMetrics {
self.running = false;
self.last_exit_reason = Some(reason.into());
self.snapshot()
}
fn snapshot(&self) -> SimulationConnectionMetrics {
SimulationConnectionMetrics {
backend: self.backend.clone(),
running: self.running,
restart_count: self.restart_count,
last_start_us: self.last_start_us,
last_exit_reason: self.last_exit_reason.clone(),
}
}
async fn publish_metrics(&self, ctx: &Context, metrics: SimulationConnectionMetrics) -> Result<()> {
let msg = DiagnosticMessage::new(&self.source, metrics);
ctx.publish_to(
Topic::<DiagnosticMessage<SimulationConnectionMetrics>>::new(TOPIC_DIAGNOSTICS_SIMULATION_CONNECTION),
&msg,
)
.await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_collector_starts_not_running_with_no_restarts() {
let collector = SimulationCollector::new("simulator", "mujoco");
assert!(!collector.running);
assert_eq!(collector.backend, "mujoco");
assert_eq!(collector.restart_count, 0);
assert_eq!(collector.last_start_us, 0);
assert!(collector.last_exit_reason.is_none());
}
#[test]
fn note_start_marks_running_and_increments_restart_count() {
let mut collector = SimulationCollector::new("simulator", "mujoco");
let metrics = collector.note_start();
assert!(metrics.running);
assert_eq!(metrics.restart_count, 1);
assert!(metrics.last_start_us > 0);
assert!(collector.running);
assert_eq!(collector.restart_count, 1);
let metrics = collector.note_start();
assert_eq!(metrics.restart_count, 2);
}
#[test]
fn note_exit_marks_not_running_and_sets_reason_without_touching_restart_count() {
let mut collector = SimulationCollector::new("simulator", "mujoco");
collector.note_start();
let metrics = collector.note_exit("exited with code 1");
assert!(!metrics.running);
assert_eq!(metrics.last_exit_reason.as_deref(), Some("exited with code 1"));
assert_eq!(metrics.restart_count, 1);
assert!(metrics.last_start_us > 0);
}
#[test]
fn snapshot_reflects_backend_identifier() {
let collector = SimulationCollector::new("simulator", "mujoco");
assert_eq!(collector.snapshot().backend, "mujoco");
}
}