-
Notifications
You must be signed in to change notification settings - Fork 1
Telemetry
Monitor P2P session performance with structured telemetry events. Track rollbacks, prediction misses, frame advances, and network stats in real time.
- Architecture
- Quick Start
SessionTelemetryTraitTelemetryEventEnumCollectingTelemetry(Built-in)- Custom Telemetry Observer
- Spec Violation Observability
- Event Flow
- Use Cases
- Integration Tips
- See Also
graph TD
P2P["P2PSession"] -->|calls| ST["SessionTelemetry trait"]
ST --> R["on_rollback"]
ST --> PM["on_prediction_miss"]
ST --> NS["on_network_stats"]
ST --> FA["on_frame_advance"]
subgraph Built-in Implementations
CT["CollectingTelemetry<br/><i>testing</i>"]
CU["Custom impl<br/><i>your own</i>"]
end
ST -.->|impl| CT
ST -.->|impl| CU
use fortress_rollback::telemetry::{CollectingTelemetry, SessionTelemetry};
use fortress_rollback::SessionBuilder;
use std::sync::Arc;
// 1. Create a telemetry observer
let telemetry = Arc::new(CollectingTelemetry::new());
// 2. Pass to session builder
// MyConfig: your Config impl (see user-guide.md)
let builder = SessionBuilder::<MyConfig>::new()
.with_telemetry(telemetry.clone());
// 3. After running the session, inspect events
let rollbacks = telemetry.rollbacks();
let misses = telemetry.prediction_misses();
println!("Rollbacks: {}, Prediction misses: {}", rollbacks.len(), misses.len());// With `sync-send` feature enabled:
pub trait SessionTelemetry: Send + Sync {
fn on_rollback(&self, depth: usize, frame: Frame) { /* no-op */ }
fn on_prediction_miss(&self, player: PlayerHandle, frame: Frame) { /* no-op */ }
fn on_network_stats(&self, player: PlayerHandle, stats: &NetworkStats) { /* no-op */ }
fn on_frame_advance(&self, frame: Frame) { /* no-op */ }
}
// Without `sync-send` feature:
pub trait SessionTelemetry {
// same methods, no Send + Sync bounds
}Note
All methods have default no-op implementations. Override only what you need. The
Send + Syncsupertraits are only required when thesync-sendfeature is enabled.
| Method | Parameters | When Called |
|---|---|---|
on_rollback |
depth: usize, frame: Frame
|
State was rolled back |
on_prediction_miss |
player: PlayerHandle, frame: Frame
|
Predicted input was wrong |
on_network_stats |
player: PlayerHandle, stats: &NetworkStats
|
Network stats polled |
on_frame_advance |
frame: Frame |
Frame advanced |
Each variant captures the arguments from its corresponding trait method.
| Variant | Fields | When |
|---|---|---|
Rollback |
depth: usize, frame: Frame
|
State was rolled back |
PredictionMiss |
player: PlayerHandle, frame: Frame
|
Predicted input was wrong |
NetworkStatsUpdate |
player: PlayerHandle, stats: NetworkStats
|
Network stats polled |
FrameAdvance |
frame: Frame |
Frame advanced |
Thread-safe observer that accumulates all events for later inspection.
| Method | Returns |
|---|---|
new() |
Empty collector |
events() |
Vec<TelemetryEvent> -- all events |
rollbacks() |
Vec<TelemetryEvent> -- filtered rollback events |
prediction_misses() |
Vec<TelemetryEvent> -- filtered prediction misses |
network_stats_updates() |
Vec<TelemetryEvent> -- filtered network stats |
frame_advances() |
Vec<TelemetryEvent> -- filtered frame advances |
len() |
usize -- event count |
is_empty() |
bool -- no events collected? |
clear() |
Clear all collected events |
Implement SessionTelemetry for your own metrics system:
use fortress_rollback::telemetry::SessionTelemetry;
use fortress_rollback::{Frame, PlayerHandle};
use fortress_rollback::NetworkStats;
use std::sync::atomic::{AtomicUsize, Ordering};
struct MetricsTelemetry {
rollback_count: AtomicUsize,
prediction_miss_count: AtomicUsize,
}
impl SessionTelemetry for MetricsTelemetry {
fn on_rollback(&self, depth: usize, _frame: Frame) {
self.rollback_count.fetch_add(1, Ordering::Relaxed);
tracing::info!(depth, "rollback occurred");
}
fn on_prediction_miss(&self, player: PlayerHandle, frame: Frame) {
self.prediction_miss_count.fetch_add(1, Ordering::Relaxed);
tracing::debug!(%player, %frame, "prediction miss");
}
}The telemetry module also provides a structured pipeline for specification violations -- internal invariant failures detected at runtime.
graph TD
LIB["Library internals"] -->|report_violation!| VO["ViolationObserver trait"]
subgraph Implementations
TO["TracingObserver<br/><i>default, logs via tracing</i>"]
CO["CollectingObserver<br/><i>testing</i>"]
MO["CompositeObserver<br/><i>multiple observers</i>"]
end
VO -.->|impl| TO
VO -.->|impl| CO
VO -.->|impl| MO
// With `sync-send` feature enabled:
pub trait ViolationObserver: Send + Sync {
fn on_violation(&self, violation: &SpecViolation);
}
// Without `sync-send` feature:
pub trait ViolationObserver {
// same method, no Send + Sync bounds
}Each violation carries structured context:
| Field | Type |
|---|---|
severity |
ViolationSeverity |
kind |
ViolationKind |
message |
String |
location |
&'static str |
frame |
Option<Frame> |
context |
BTreeMap<String, String> |
Builder methods:
| Method | Description |
|---|---|
new(severity, kind, message, location) |
Create a new violation |
with_frame(frame) |
Attach a frame reference |
with_context(key, value) |
Add a key-value context entry |
to_json() |
Option<String> -- JSON string (requires json feature) |
to_json_pretty() |
Option<String> -- pretty JSON string (requires json feature) |
Thread-safe observer that accumulates all violations for later inspection.
| Method | Returns |
|---|---|
new() |
Empty collector |
violations() |
Vec<SpecViolation> — all collected violations |
len() |
Number of violations |
is_empty() |
No violations collected? |
has_violation(kind) |
Any violation of this kind? |
has_severity(severity) |
Any violation at this severity? |
violations_of_kind(kind) |
Filtered by kind |
violations_at_severity(min) |
Filtered by minimum severity |
clear() |
Remove all collected violations |
Default observer that maps severity levels to tracing log levels: Warning → tracing::warn!, Error/Critical → tracing::error!. All fields are emitted as structured tracing fields.
use fortress_rollback::telemetry::CollectingObserver;
use fortress_rollback::SessionBuilder;
use std::sync::Arc;
let observer = Arc::new(CollectingObserver::new());
// MyConfig: your Config impl (see user-guide.md)
let builder = SessionBuilder::<MyConfig>::new()
.with_violation_observer(observer.clone());
// After session operations
assert!(observer.violations().is_empty(), "unexpected violations");| Variant | Description |
|---|---|
FrameSync |
Frame synchronization invariant violated |
InputQueue |
Input queue invariant violated |
StateManagement |
State save/load invariant violated |
NetworkProtocol |
Network protocol invariant violated |
ChecksumMismatch |
Checksum or desync detection issue |
Configuration |
Configuration constraint violated |
InternalError |
Internal logic error (library bug) |
Invariant |
Runtime invariant check failed |
Synchronization |
Sync protocol issues |
ArithmeticOverflow |
Arithmetic overflow detected |
| Level | Meaning |
|---|---|
Warning |
Unexpected but recoverable -- operation continued with fallback |
Error |
Serious issue -- operation may have degraded behavior |
Critical |
Critical invariant broken -- state may be corrupted |
sequenceDiagram
participant Game as Game Loop
participant Session as P2PSession
participant Telemetry as SessionTelemetry
Game->>Session: advance_frame()
Session->>Telemetry: on_frame_advance(frame)
Note over Session: Remote input arrives late
Session->>Telemetry: on_prediction_miss(player, frame)
Session->>Telemetry: on_rollback(depth, target_frame)
Note over Session: Re-simulate frames
loop For each re-simulated frame
Session->>Telemetry: on_frame_advance(frame)
end
Game->>Session: network_stats(player)
Session->>Telemetry: on_network_stats(player, stats)
- Performance monitoring -- Track rollback frequency and prediction accuracy over time
-
Network quality dashboards -- Aggregate
NetworkStatsUpdateevents per peer -
Automated testing assertions -- Use
CollectingTelemetryto assert rollback counts, prediction accuracy - Debug overlays -- Display rollback count, ping, and frame advantage in a HUD
Performance
Keep observer callbacks fast -- they run inline during frame processing. Offload heavy work (file I/O, network sends) to a background thread.
Tip
Use
Arc<CollectingTelemetry>for testing, a customSessionTelemetryimpl for production.Safety
Both
SessionTelemetryandViolationObserverrequireSend + Syncwhen thesync-sendfeature is enabled. Thesync-sendfeature is not a default feature and must be explicitly opted into. All built-in implementations are thread-safe regardless of feature flags.
- User Guide — integrating Fortress Rollback into your game
- Architecture Overview — system design and module structure