bevy_fleet/
session.rs

1use bevy::prelude::*;
2use serde::{Deserialize, Serialize};
3use std::time::{Instant, SystemTime, UNIX_EPOCH};
4
5/// Session information for correlating metrics across a game session
6#[derive(Clone, Debug, Resource, Serialize, Deserialize)]
7pub struct SessionInfo {
8    /// Unique identifier for this session
9    pub session_id: String,
10
11    /// Unix timestamp (seconds) when the session started
12    pub session_start_time: u64,
13
14    /// Internal wall-clock reference used to compute uptime accurately
15    #[serde(
16        skip_serializing,
17        skip_deserializing,
18        default = "default_session_instant"
19    )]
20    pub(crate) session_start_instant: Instant,
21
22    /// Session statistics
23    pub stats: SessionStats,
24}
25
26/// Statistics tracked for the current session
27#[derive(Clone, Debug, Default, Serialize, Deserialize)]
28pub struct SessionStats {
29    /// Total number of telemetry payloads published in this session
30    pub payloads_published: u64,
31
32    /// Total number of metrics collected in this session
33    pub metrics_collected: u64,
34
35    /// Total number of events tracked in this session
36    pub events_tracked: u64,
37
38    /// Total number of diagnostics recorded in this session
39    pub diagnostics_recorded: u64,
40
41    /// Number of panics captured in this session
42    pub panics_captured: u64,
43
44    /// Session uptime in seconds (updated on each publish)
45    pub uptime_seconds: u64,
46}
47
48impl SessionInfo {
49    /// Creates a new session with a unique ID
50    pub fn new() -> Self {
51        let session_id = generate_session_id();
52        let session_start_time = SystemTime::now()
53            .duration_since(UNIX_EPOCH)
54            .expect("System time is before UNIX epoch")
55            .as_secs();
56
57        info!("New session created: {}", session_id);
58
59        Self {
60            session_id,
61            session_start_time,
62            session_start_instant: Instant::now(),
63            stats: SessionStats::default(),
64        }
65    }
66
67    /// Updates session stats based on the current payload
68    pub fn update_stats(
69        &mut self,
70        metrics_count: usize,
71        events_count: usize,
72        diagnostics_count: usize,
73        panics_count: usize,
74    ) {
75        self.stats.payloads_published += 1;
76        self.stats.metrics_collected += metrics_count as u64;
77        self.stats.events_tracked += events_count as u64;
78        self.stats.diagnostics_recorded += diagnostics_count as u64;
79        self.stats.panics_captured += panics_count as u64;
80
81        // Update uptime using the monotonic clock to avoid drift from system time changes.
82        self.stats.uptime_seconds = self.session_start_instant.elapsed().as_secs();
83    }
84}
85
86impl Default for SessionInfo {
87    fn default() -> Self {
88        Self::new()
89    }
90}
91
92/// Generates a unique session ID using UUID v4
93fn generate_session_id() -> String {
94    use std::time::SystemTime;
95
96    // Generate a simple session ID based on timestamp and random data
97    let now = SystemTime::now()
98        .duration_since(UNIX_EPOCH)
99        .expect("System time is before UNIX epoch");
100
101    // Use a combination of timestamp and process ID for uniqueness
102    let pid = std::process::id();
103    let nanos = now.subsec_nanos();
104
105    format!("session-{:x}-{:x}-{:x}", now.as_secs(), pid, nanos)
106}
107
108/// Initializes the session info resource
109pub fn initialize_session(app: &mut App) {
110    let session_info = SessionInfo::new();
111    app.insert_resource(session_info);
112}
113
114fn default_session_instant() -> Instant {
115    Instant::now()
116}