bevy_fleet 0.1.0

bevy swarm diagnostic, event, metric, and telemetry client
Documentation
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use std::time::{Instant, SystemTime, UNIX_EPOCH};

/// Session information for correlating metrics across a game session
#[derive(Clone, Debug, Resource, Serialize, Deserialize)]
pub struct SessionInfo {
    /// Unique identifier for this session
    pub session_id: String,

    /// Unix timestamp (seconds) when the session started
    pub session_start_time: u64,

    /// Internal wall-clock reference used to compute uptime accurately
    #[serde(
        skip_serializing,
        skip_deserializing,
        default = "default_session_instant"
    )]
    pub(crate) session_start_instant: Instant,

    /// Session statistics
    pub stats: SessionStats,
}

/// Statistics tracked for the current session
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SessionStats {
    /// Total number of telemetry payloads published in this session
    pub payloads_published: u64,

    /// Total number of metrics collected in this session
    pub metrics_collected: u64,

    /// Total number of events tracked in this session
    pub events_tracked: u64,

    /// Total number of diagnostics recorded in this session
    pub diagnostics_recorded: u64,

    /// Number of panics captured in this session
    pub panics_captured: u64,

    /// Session uptime in seconds (updated on each publish)
    pub uptime_seconds: u64,
}

impl SessionInfo {
    /// Creates a new session with a unique ID
    pub fn new() -> Self {
        let session_id = generate_session_id();
        let session_start_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("System time is before UNIX epoch")
            .as_secs();

        info!("New session created: {}", session_id);

        Self {
            session_id,
            session_start_time,
            session_start_instant: Instant::now(),
            stats: SessionStats::default(),
        }
    }

    /// Updates session stats based on the current payload
    pub fn update_stats(
        &mut self,
        metrics_count: usize,
        events_count: usize,
        diagnostics_count: usize,
        panics_count: usize,
    ) {
        self.stats.payloads_published += 1;
        self.stats.metrics_collected += metrics_count as u64;
        self.stats.events_tracked += events_count as u64;
        self.stats.diagnostics_recorded += diagnostics_count as u64;
        self.stats.panics_captured += panics_count as u64;

        // Update uptime using the monotonic clock to avoid drift from system time changes.
        self.stats.uptime_seconds = self.session_start_instant.elapsed().as_secs();
    }
}

impl Default for SessionInfo {
    fn default() -> Self {
        Self::new()
    }
}

/// Generates a unique session ID using UUID v4
fn generate_session_id() -> String {
    use std::time::SystemTime;

    // Generate a simple session ID based on timestamp and random data
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("System time is before UNIX epoch");

    // Use a combination of timestamp and process ID for uniqueness
    let pid = std::process::id();
    let nanos = now.subsec_nanos();

    format!("session-{:x}-{:x}-{:x}", now.as_secs(), pid, nanos)
}

/// Initializes the session info resource
pub fn initialize_session(app: &mut App) {
    let session_info = SessionInfo::new();
    app.insert_resource(session_info);
}

fn default_session_instant() -> Instant {
    Instant::now()
}