dev/render-worker-establishment #32
@ -23,6 +23,7 @@ pub mod platform;
|
||||
pub mod primitives;
|
||||
pub mod render_resources;
|
||||
pub mod render_submission;
|
||||
pub mod render_worker;
|
||||
pub mod sample;
|
||||
pub mod scene_bank;
|
||||
pub mod scene_layer;
|
||||
@ -59,4 +60,5 @@ pub use render_submission::{
|
||||
Gfx2dCommand, GfxUiCommand, HudCommand, HudPacket, RenderOwnership, RenderSubmission,
|
||||
RenderSubmissionPacket, ShellUiFramePacket,
|
||||
};
|
||||
pub use render_worker::{RenderWorkerError, RenderWorkerTelemetry};
|
||||
pub use touch_bridge::TouchBridge;
|
||||
|
||||
190
crates/console/prometeu-hal/src/render_worker.rs
Normal file
190
crates/console/prometeu-hal/src/render_worker.rs
Normal file
@ -0,0 +1,190 @@
|
||||
use std::fmt;
|
||||
|
||||
/// Typed failure surface for the real render worker and its backend.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RenderWorkerError {
|
||||
BackendUnavailable,
|
||||
RenderFailed,
|
||||
PublishFailed,
|
||||
PresentFailed,
|
||||
ShutdownTimeout,
|
||||
StaleSubmissionDiscarded,
|
||||
WorkerPanic,
|
||||
InternalFailure,
|
||||
}
|
||||
|
||||
impl fmt::Display for RenderWorkerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::BackendUnavailable => f.write_str("render backend unavailable"),
|
||||
Self::RenderFailed => f.write_str("render failed"),
|
||||
Self::PublishFailed => f.write_str("render publish failed"),
|
||||
Self::PresentFailed => f.write_str("render present failed"),
|
||||
Self::ShutdownTimeout => f.write_str("render worker shutdown timed out"),
|
||||
Self::StaleSubmissionDiscarded => f.write_str("stale render submission discarded"),
|
||||
Self::WorkerPanic => f.write_str("render worker panicked"),
|
||||
Self::InternalFailure => f.write_str("internal render worker failure"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for RenderWorkerError {}
|
||||
|
||||
/// Snapshot of render worker handoff, render, publish, and shutdown counters.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub struct RenderWorkerTelemetry {
|
||||
pub produced_submissions: u64,
|
||||
pub replaced_before_consume: u64,
|
||||
pub consumed_submissions: u64,
|
||||
pub published_frames: u64,
|
||||
pub presented_frames: u64,
|
||||
pub repeated_frames: u64,
|
||||
pub stale_submission_discards: u64,
|
||||
pub render_failures: u64,
|
||||
pub publish_failures: u64,
|
||||
pub present_failures: u64,
|
||||
pub shutdown_timeouts: u64,
|
||||
pub worker_panics: u64,
|
||||
pub last_produced_frame_id: u64,
|
||||
pub last_consumed_frame_id: u64,
|
||||
pub last_published_frame_id: u64,
|
||||
pub last_presented_frame_id: u64,
|
||||
pub last_dropped_frame_id: u64,
|
||||
pub last_error_frame_id: u64,
|
||||
pub active_render_epoch: u64,
|
||||
}
|
||||
|
||||
impl RenderWorkerTelemetry {
|
||||
pub fn record_produced(&mut self, frame_id: u64) {
|
||||
self.produced_submissions += 1;
|
||||
self.last_produced_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_replaced_before_consume(&mut self, frame_id: u64) {
|
||||
self.replaced_before_consume += 1;
|
||||
self.last_dropped_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_consumed(&mut self, frame_id: u64) {
|
||||
self.consumed_submissions += 1;
|
||||
self.last_consumed_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_published(&mut self, frame_id: u64) {
|
||||
self.published_frames += 1;
|
||||
self.last_published_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_presented(&mut self, frame_id: u64) {
|
||||
self.presented_frames += 1;
|
||||
self.last_presented_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_repeated_frame(&mut self, frame_id: u64) {
|
||||
self.repeated_frames += 1;
|
||||
self.last_presented_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_stale_submission_discarded(&mut self, frame_id: u64) {
|
||||
self.stale_submission_discards += 1;
|
||||
self.last_dropped_frame_id = frame_id;
|
||||
}
|
||||
|
||||
pub fn record_error(&mut self, error: RenderWorkerError, frame_id: u64) {
|
||||
match error {
|
||||
RenderWorkerError::BackendUnavailable | RenderWorkerError::RenderFailed => {
|
||||
self.render_failures += 1;
|
||||
}
|
||||
RenderWorkerError::PublishFailed => {
|
||||
self.publish_failures += 1;
|
||||
}
|
||||
RenderWorkerError::PresentFailed => {
|
||||
self.present_failures += 1;
|
||||
}
|
||||
RenderWorkerError::ShutdownTimeout => {
|
||||
self.shutdown_timeouts += 1;
|
||||
}
|
||||
RenderWorkerError::WorkerPanic => {
|
||||
self.worker_panics += 1;
|
||||
}
|
||||
RenderWorkerError::StaleSubmissionDiscarded => {
|
||||
self.stale_submission_discards += 1;
|
||||
self.last_dropped_frame_id = frame_id;
|
||||
}
|
||||
RenderWorkerError::InternalFailure => {
|
||||
self.render_failures += 1;
|
||||
}
|
||||
}
|
||||
self.last_error_frame_id = frame_id;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn render_worker_error_is_copy_equatable_and_debuggable() {
|
||||
let error = RenderWorkerError::ShutdownTimeout;
|
||||
let copied = error;
|
||||
|
||||
assert_eq!(copied, RenderWorkerError::ShutdownTimeout);
|
||||
assert_eq!(format!("{:?}", error), "ShutdownTimeout");
|
||||
assert_eq!(error.to_string(), "render worker shutdown timed out");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_worker_telemetry_defaults_to_zero() {
|
||||
assert_eq!(
|
||||
RenderWorkerTelemetry::default(),
|
||||
RenderWorkerTelemetry {
|
||||
produced_submissions: 0,
|
||||
replaced_before_consume: 0,
|
||||
consumed_submissions: 0,
|
||||
published_frames: 0,
|
||||
presented_frames: 0,
|
||||
repeated_frames: 0,
|
||||
stale_submission_discards: 0,
|
||||
render_failures: 0,
|
||||
publish_failures: 0,
|
||||
present_failures: 0,
|
||||
shutdown_timeouts: 0,
|
||||
worker_panics: 0,
|
||||
last_produced_frame_id: 0,
|
||||
last_consumed_frame_id: 0,
|
||||
last_published_frame_id: 0,
|
||||
last_presented_frame_id: 0,
|
||||
last_dropped_frame_id: 0,
|
||||
last_error_frame_id: 0,
|
||||
active_render_epoch: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_worker_telemetry_records_counters_and_frame_ids() {
|
||||
let mut telemetry = RenderWorkerTelemetry::default();
|
||||
|
||||
telemetry.record_produced(10);
|
||||
telemetry.record_replaced_before_consume(10);
|
||||
telemetry.record_consumed(11);
|
||||
telemetry.record_published(11);
|
||||
telemetry.record_presented(11);
|
||||
telemetry.record_repeated_frame(11);
|
||||
telemetry.record_error(RenderWorkerError::PublishFailed, 12);
|
||||
telemetry.record_error(RenderWorkerError::WorkerPanic, 13);
|
||||
telemetry.record_stale_submission_discarded(14);
|
||||
|
||||
assert_eq!(telemetry.produced_submissions, 1);
|
||||
assert_eq!(telemetry.replaced_before_consume, 1);
|
||||
assert_eq!(telemetry.consumed_submissions, 1);
|
||||
assert_eq!(telemetry.published_frames, 1);
|
||||
assert_eq!(telemetry.presented_frames, 1);
|
||||
assert_eq!(telemetry.repeated_frames, 1);
|
||||
assert_eq!(telemetry.publish_failures, 1);
|
||||
assert_eq!(telemetry.worker_panics, 1);
|
||||
assert_eq!(telemetry.stale_submission_discards, 1);
|
||||
assert_eq!(telemetry.last_error_frame_id, 13);
|
||||
assert_eq!(telemetry.last_dropped_frame_id, 14);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::log::{LogLevel, LogService, LogSource};
|
||||
use crate::render_worker::RenderWorkerTelemetry;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
|
||||
|
||||
@ -34,20 +35,51 @@ pub struct TelemetryFrame {
|
||||
pub produced_submissions: u64,
|
||||
pub replaced_before_consume: u64,
|
||||
pub consumed_submissions: u64,
|
||||
pub published_frames: u64,
|
||||
pub presented_frames: u64,
|
||||
pub repeated_presents: u64,
|
||||
pub render_errors: u64,
|
||||
pub publish_errors: u64,
|
||||
pub present_errors: u64,
|
||||
pub stale_epoch_discards: u64,
|
||||
pub shutdown_discards: u64,
|
||||
pub shutdown_timeouts: u64,
|
||||
pub worker_panics: u64,
|
||||
pub last_produced_frame_id: u64,
|
||||
pub last_consumed_frame_id: u64,
|
||||
pub last_published_frame_id: u64,
|
||||
pub last_presented_frame_id: u64,
|
||||
pub last_dropped_frame_id: u64,
|
||||
pub last_error_frame_id: u64,
|
||||
pub active_render_epoch: u64,
|
||||
}
|
||||
|
||||
impl TelemetryFrame {
|
||||
pub fn render_worker_telemetry(&self) -> RenderWorkerTelemetry {
|
||||
RenderWorkerTelemetry {
|
||||
produced_submissions: self.produced_submissions,
|
||||
replaced_before_consume: self.replaced_before_consume,
|
||||
consumed_submissions: self.consumed_submissions,
|
||||
published_frames: self.published_frames,
|
||||
presented_frames: self.presented_frames,
|
||||
repeated_frames: self.repeated_presents,
|
||||
stale_submission_discards: self.stale_epoch_discards,
|
||||
render_failures: self.render_errors,
|
||||
publish_failures: self.publish_errors,
|
||||
present_failures: self.present_errors,
|
||||
shutdown_timeouts: self.shutdown_timeouts,
|
||||
worker_panics: self.worker_panics,
|
||||
last_produced_frame_id: self.last_produced_frame_id,
|
||||
last_consumed_frame_id: self.last_consumed_frame_id,
|
||||
last_published_frame_id: self.last_published_frame_id,
|
||||
last_presented_frame_id: self.last_presented_frame_id,
|
||||
last_dropped_frame_id: self.last_dropped_frame_id,
|
||||
last_error_frame_id: self.last_error_frame_id,
|
||||
active_render_epoch: self.active_render_epoch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe, atomic telemetry storage for real-time monitoring by the host.
|
||||
/// This follows the push-based model from DEC-0005 to avoid expensive scans or locks.
|
||||
#[derive(Debug, Default)]
|
||||
@ -84,14 +116,19 @@ pub struct AtomicTelemetry {
|
||||
pub produced_submissions: AtomicU64,
|
||||
pub replaced_before_consume: AtomicU64,
|
||||
pub consumed_submissions: AtomicU64,
|
||||
pub published_frames: AtomicU64,
|
||||
pub presented_frames: AtomicU64,
|
||||
pub repeated_presents: AtomicU64,
|
||||
pub render_errors: AtomicU64,
|
||||
pub publish_errors: AtomicU64,
|
||||
pub present_errors: AtomicU64,
|
||||
pub stale_epoch_discards: AtomicU64,
|
||||
pub shutdown_discards: AtomicU64,
|
||||
pub shutdown_timeouts: AtomicU64,
|
||||
pub worker_panics: AtomicU64,
|
||||
pub last_produced_frame_id: AtomicU64,
|
||||
pub last_consumed_frame_id: AtomicU64,
|
||||
pub last_published_frame_id: AtomicU64,
|
||||
pub last_presented_frame_id: AtomicU64,
|
||||
pub last_dropped_frame_id: AtomicU64,
|
||||
pub last_error_frame_id: AtomicU64,
|
||||
@ -128,14 +165,19 @@ impl AtomicTelemetry {
|
||||
produced_submissions: self.produced_submissions.load(Ordering::Relaxed),
|
||||
replaced_before_consume: self.replaced_before_consume.load(Ordering::Relaxed),
|
||||
consumed_submissions: self.consumed_submissions.load(Ordering::Relaxed),
|
||||
published_frames: self.published_frames.load(Ordering::Relaxed),
|
||||
presented_frames: self.presented_frames.load(Ordering::Relaxed),
|
||||
repeated_presents: self.repeated_presents.load(Ordering::Relaxed),
|
||||
render_errors: self.render_errors.load(Ordering::Relaxed),
|
||||
publish_errors: self.publish_errors.load(Ordering::Relaxed),
|
||||
present_errors: self.present_errors.load(Ordering::Relaxed),
|
||||
stale_epoch_discards: self.stale_epoch_discards.load(Ordering::Relaxed),
|
||||
shutdown_discards: self.shutdown_discards.load(Ordering::Relaxed),
|
||||
shutdown_timeouts: self.shutdown_timeouts.load(Ordering::Relaxed),
|
||||
worker_panics: self.worker_panics.load(Ordering::Relaxed),
|
||||
last_produced_frame_id: self.last_produced_frame_id.load(Ordering::Relaxed),
|
||||
last_consumed_frame_id: self.last_consumed_frame_id.load(Ordering::Relaxed),
|
||||
last_published_frame_id: self.last_published_frame_id.load(Ordering::Relaxed),
|
||||
last_presented_frame_id: self.last_presented_frame_id.load(Ordering::Relaxed),
|
||||
last_dropped_frame_id: self.last_dropped_frame_id.load(Ordering::Relaxed),
|
||||
last_error_frame_id: self.last_error_frame_id.load(Ordering::Relaxed),
|
||||
@ -165,14 +207,19 @@ impl AtomicTelemetry {
|
||||
self.produced_submissions.store(0, Ordering::Relaxed);
|
||||
self.replaced_before_consume.store(0, Ordering::Relaxed);
|
||||
self.consumed_submissions.store(0, Ordering::Relaxed);
|
||||
self.published_frames.store(0, Ordering::Relaxed);
|
||||
self.presented_frames.store(0, Ordering::Relaxed);
|
||||
self.repeated_presents.store(0, Ordering::Relaxed);
|
||||
self.render_errors.store(0, Ordering::Relaxed);
|
||||
self.publish_errors.store(0, Ordering::Relaxed);
|
||||
self.present_errors.store(0, Ordering::Relaxed);
|
||||
self.stale_epoch_discards.store(0, Ordering::Relaxed);
|
||||
self.shutdown_discards.store(0, Ordering::Relaxed);
|
||||
self.shutdown_timeouts.store(0, Ordering::Relaxed);
|
||||
self.worker_panics.store(0, Ordering::Relaxed);
|
||||
self.last_produced_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_consumed_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_published_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_presented_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_dropped_frame_id.store(0, Ordering::Relaxed);
|
||||
self.last_error_frame_id.store(0, Ordering::Relaxed);
|
||||
@ -398,4 +445,51 @@ mod tests {
|
||||
assert_eq!(snapshot.vm_heap_allocations, 2);
|
||||
assert_eq!(snapshot.vm_string_materializations, 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snapshot_maps_render_worker_telemetry() {
|
||||
let current = Arc::new(AtomicU32::new(0));
|
||||
let tel = AtomicTelemetry::new(current);
|
||||
tel.produced_submissions.store(2, Ordering::Relaxed);
|
||||
tel.replaced_before_consume.store(1, Ordering::Relaxed);
|
||||
tel.consumed_submissions.store(1, Ordering::Relaxed);
|
||||
tel.published_frames.store(1, Ordering::Relaxed);
|
||||
tel.presented_frames.store(1, Ordering::Relaxed);
|
||||
tel.repeated_presents.store(3, Ordering::Relaxed);
|
||||
tel.render_errors.store(4, Ordering::Relaxed);
|
||||
tel.publish_errors.store(5, Ordering::Relaxed);
|
||||
tel.present_errors.store(6, Ordering::Relaxed);
|
||||
tel.stale_epoch_discards.store(7, Ordering::Relaxed);
|
||||
tel.shutdown_timeouts.store(8, Ordering::Relaxed);
|
||||
tel.worker_panics.store(9, Ordering::Relaxed);
|
||||
tel.last_produced_frame_id.store(10, Ordering::Relaxed);
|
||||
tel.last_consumed_frame_id.store(11, Ordering::Relaxed);
|
||||
tel.last_published_frame_id.store(12, Ordering::Relaxed);
|
||||
tel.last_presented_frame_id.store(13, Ordering::Relaxed);
|
||||
tel.last_dropped_frame_id.store(14, Ordering::Relaxed);
|
||||
tel.last_error_frame_id.store(15, Ordering::Relaxed);
|
||||
tel.active_render_epoch.store(16, Ordering::Relaxed);
|
||||
|
||||
let worker = tel.snapshot().render_worker_telemetry();
|
||||
|
||||
assert_eq!(worker.produced_submissions, 2);
|
||||
assert_eq!(worker.replaced_before_consume, 1);
|
||||
assert_eq!(worker.consumed_submissions, 1);
|
||||
assert_eq!(worker.published_frames, 1);
|
||||
assert_eq!(worker.presented_frames, 1);
|
||||
assert_eq!(worker.repeated_frames, 3);
|
||||
assert_eq!(worker.render_failures, 4);
|
||||
assert_eq!(worker.publish_failures, 5);
|
||||
assert_eq!(worker.present_failures, 6);
|
||||
assert_eq!(worker.stale_submission_discards, 7);
|
||||
assert_eq!(worker.shutdown_timeouts, 8);
|
||||
assert_eq!(worker.worker_panics, 9);
|
||||
assert_eq!(worker.last_produced_frame_id, 10);
|
||||
assert_eq!(worker.last_consumed_frame_id, 11);
|
||||
assert_eq!(worker.last_published_frame_id, 12);
|
||||
assert_eq!(worker.last_presented_frame_id, 13);
|
||||
assert_eq!(worker.last_dropped_frame_id, 14);
|
||||
assert_eq!(worker.last_error_frame_id, 15);
|
||||
assert_eq!(worker.active_render_epoch, 16);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
id: PLN-0112
|
||||
ticket: real-render-worker-establishment
|
||||
title: Define Render Worker Errors and Telemetry
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-06-15
|
||||
completed:
|
||||
ref_decisions: [DEC-0033]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user