impl SnapshotReport { pub fn from_state(state: &LauncherState, log: &DiagnosticsLog, probe_command: String) -> Self { let left_capture = state .display_capture_size_choice(0) .unwrap_or_else(|| state.capture_size_choice(0)); let right_capture = state .display_capture_size_choice(1) .unwrap_or_else(|| state.capture_size_choice(1)); let left_breakout = state.breakout_size_choice(0); let right_breakout = state.breakout_size_choice(1); let latest = log.latest(); let left_stream_caps = latest .map(|sample| sample.left_stream_caps_label.clone()) .unwrap_or_default(); let right_stream_caps = latest .map(|sample| sample.right_stream_caps_label.clone()) .unwrap_or_default(); Self { client_version: crate::VERSION.to_string(), server_version: state.server_version.clone(), server_available: state.server_available, routing: state.routing, view_mode: state.view_mode, remote_active: state.remote_active, power_state: format!( "{} | {} | leases {}", state.capture_power.mode, state.capture_power.detail, state.capture_power.active_leases ), client_process_cpu_pct: latest .map(|sample| sample.client_process_cpu_pct) .unwrap_or(0.0), server_process_cpu_pct: latest .map(|sample| sample.server_process_cpu_pct) .unwrap_or(0.0), preview_source: format!( "{}x{} @ {} fps", state.preview_source.width, state.preview_source.height, state.preview_source.fps ), client_display_limit: format!( "{}x{}", state.breakout_display.width, state.breakout_display.height ), left_surface: state.display_surface(0).label().to_string(), left_feed_source: match state.feed_source_preset(0) { super::state::FeedSourcePreset::ThisEye => "Left Eye".to_string(), super::state::FeedSourcePreset::OtherEye => "Right Eye (mirrored)".to_string(), super::state::FeedSourcePreset::Off => "Off".to_string(), }, left_capture_profile: capture_profile_label(&left_capture, &left_stream_caps), left_capture_transport: left_capture.preset.transport_label().to_string(), left_breakout_profile: format!( "{} | {}x{}", left_breakout.preset.label(), left_breakout.width, left_breakout.height ), left_decoder_label: latest .map(|sample| { if sample.left_decoder_label.is_empty() { "pending".to_string() } else { sample.left_decoder_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), left_stream_spread_ms: latest .map(|sample| sample.left_stream_spread_ms) .unwrap_or(0.0), left_packet_gap_peak_ms: latest .map(|sample| sample.left_packet_gap_peak_ms) .unwrap_or(0.0), left_present_gap_peak_ms: latest .map(|sample| sample.left_present_gap_peak_ms) .unwrap_or(0.0), left_queue_depth: latest.map(|sample| sample.left_queue_depth).unwrap_or(0), left_queue_peak: latest.map(|sample| sample.left_queue_peak).unwrap_or(0), left_server_source_gap_peak_ms: latest .map(|sample| sample.left_server_source_gap_peak_ms) .unwrap_or(0.0), left_server_send_gap_peak_ms: latest .map(|sample| sample.left_server_send_gap_peak_ms) .unwrap_or(0.0), left_server_queue_peak: latest .map(|sample| sample.left_server_queue_peak) .unwrap_or(0), left_server_encoder_label: latest .map(|sample| { if sample.left_server_encoder_label.is_empty() { "pending".to_string() } else { sample.left_server_encoder_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), left_stream_caps_label: latest .map(|sample| { if sample.left_stream_caps_label.is_empty() { "pending".to_string() } else { sample.left_stream_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), left_decoded_caps_label: latest .map(|sample| { if sample.left_decoded_caps_label.is_empty() { "pending".to_string() } else { sample.left_decoded_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), left_rendered_caps_label: latest .map(|sample| { if sample.left_rendered_caps_label.is_empty() { "pending".to_string() } else { sample.left_rendered_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), right_surface: state.display_surface(1).label().to_string(), right_feed_source: match state.feed_source_preset(1) { super::state::FeedSourcePreset::ThisEye => "Right Eye".to_string(), super::state::FeedSourcePreset::OtherEye => "Left Eye (mirrored)".to_string(), super::state::FeedSourcePreset::Off => "Off".to_string(), }, right_capture_profile: capture_profile_label(&right_capture, &right_stream_caps), right_capture_transport: right_capture.preset.transport_label().to_string(), right_breakout_profile: format!( "{} | {}x{}", right_breakout.preset.label(), right_breakout.width, right_breakout.height ), right_decoder_label: latest .map(|sample| { if sample.right_decoder_label.is_empty() { "pending".to_string() } else { sample.right_decoder_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), right_stream_spread_ms: latest .map(|sample| sample.right_stream_spread_ms) .unwrap_or(0.0), right_packet_gap_peak_ms: latest .map(|sample| sample.right_packet_gap_peak_ms) .unwrap_or(0.0), right_present_gap_peak_ms: latest .map(|sample| sample.right_present_gap_peak_ms) .unwrap_or(0.0), right_queue_depth: latest.map(|sample| sample.right_queue_depth).unwrap_or(0), right_queue_peak: latest.map(|sample| sample.right_queue_peak).unwrap_or(0), right_server_source_gap_peak_ms: latest .map(|sample| sample.right_server_source_gap_peak_ms) .unwrap_or(0.0), right_server_send_gap_peak_ms: latest .map(|sample| sample.right_server_send_gap_peak_ms) .unwrap_or(0.0), right_server_queue_peak: latest .map(|sample| sample.right_server_queue_peak) .unwrap_or(0), right_server_encoder_label: latest .map(|sample| { if sample.right_server_encoder_label.is_empty() { "pending".to_string() } else { sample.right_server_encoder_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), right_stream_caps_label: latest .map(|sample| { if sample.right_stream_caps_label.is_empty() { "pending".to_string() } else { sample.right_stream_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), right_decoded_caps_label: latest .map(|sample| { if sample.right_decoded_caps_label.is_empty() { "pending".to_string() } else { sample.right_decoded_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), right_rendered_caps_label: latest .map(|sample| { if sample.right_rendered_caps_label.is_empty() { "pending".to_string() } else { sample.right_rendered_caps_label.clone() } }) .unwrap_or_else(|| "pending".to_string()), selected_camera: state.devices.camera.clone(), camera_quality_label: state .camera_quality .map(CameraMode::short_label) .unwrap_or_else(|| "default".to_string()), selected_microphone: state.devices.microphone.clone(), selected_speaker: state.devices.speaker.clone(), media_channels: MediaChannelState { camera: state.channels.camera, microphone: state.channels.microphone, audio: state.channels.audio, }, audio_gain_label: state.audio_gain_label(), mic_gain_label: state.mic_gain_label(), upstream_camera: latest .map(|sample| sample.upstream_camera.clone()) .unwrap_or_default(), upstream_microphone: latest .map(|sample| sample.upstream_microphone.clone()) .unwrap_or_default(), selected_keyboard: state.devices.keyboard.clone(), selected_mouse: state.devices.mouse.clone(), status: state.status_line(), recent_samples: log.iter().cloned().collect(), notes: state.notes.clone(), recommendations: recommendations_for(state, log), probe_command, } } pub fn to_pretty_json(&self) -> Result { serde_json::to_string_pretty(self) } pub fn to_pretty_text(&self) -> String { let mut text = String::new(); let server_version = self.server_version.as_deref().unwrap_or("unknown"); let server_state = if self.server_available { "reachable" } else { "unreachable" }; let _ = writeln!(text, "Lesavka Diagnostics"); let _ = writeln!(text, "client: v{}", self.client_version); let _ = writeln!(text, "server: {server_version} ({server_state})"); let _ = writeln!( text, "session: routing={:?} view={:?} relay={} capture_power={}", self.routing, self.view_mode, if self.remote_active { "active" } else { "idle" }, self.power_state ); let _ = writeln!( text, "runtime: client CPU {:.1}% | server CPU {:.1}%", self.client_process_cpu_pct, self.server_process_cpu_pct ); let _ = writeln!(text, "source feed: {}", self.preview_source); let _ = writeln!(text, "display limit: {}", self.client_display_limit); let _ = writeln!(text); let _ = writeln!(text, "left eye"); let _ = writeln!(text, " surface: {}", self.left_surface); let _ = writeln!(text, " source: {}", self.left_feed_source); let _ = writeln!(text, " capture: {}", self.left_capture_profile); let _ = writeln!(text, " transport: {}", self.left_capture_transport); let _ = writeln!(text, " breakout: {}", self.left_breakout_profile); let _ = writeln!( text, " live: decoder={} spread={:.1}ms gaps={:.0}/{:.0}ms queue={}/{}", self.left_decoder_label, self.left_stream_spread_ms, self.left_packet_gap_peak_ms, self.left_present_gap_peak_ms, self.left_queue_depth, self.left_queue_peak ); let _ = writeln!(text, " stream caps: {}", self.left_stream_caps_label); let _ = writeln!(text, " decoded caps: {}", self.left_decoded_caps_label); let _ = writeln!(text, " rendered caps: {}", self.left_rendered_caps_label); let _ = writeln!( text, " server: encoder={} cpu={:.1}% gaps={:.0}/{:.0}ms queue-peak={}", self.left_server_encoder_label, self.server_process_cpu_pct, self.left_server_source_gap_peak_ms, self.left_server_send_gap_peak_ms, self.left_server_queue_peak ); let _ = writeln!(text, "right eye"); let _ = writeln!(text, " surface: {}", self.right_surface); let _ = writeln!(text, " source: {}", self.right_feed_source); let _ = writeln!(text, " capture: {}", self.right_capture_profile); let _ = writeln!(text, " transport: {}", self.right_capture_transport); let _ = writeln!(text, " breakout: {}", self.right_breakout_profile); let _ = writeln!( text, " live: decoder={} spread={:.1}ms gaps={:.0}/{:.0}ms queue={}/{}", self.right_decoder_label, self.right_stream_spread_ms, self.right_packet_gap_peak_ms, self.right_present_gap_peak_ms, self.right_queue_depth, self.right_queue_peak ); let _ = writeln!(text, " stream caps: {}", self.right_stream_caps_label); let _ = writeln!(text, " decoded caps: {}", self.right_decoded_caps_label); let _ = writeln!(text, " rendered caps: {}", self.right_rendered_caps_label); let _ = writeln!( text, " server: encoder={} cpu={:.1}% gaps={:.0}/{:.0}ms queue-peak={}", self.right_server_encoder_label, self.server_process_cpu_pct, self.right_server_source_gap_peak_ms, self.right_server_send_gap_peak_ms, self.right_server_queue_peak ); let _ = writeln!(text); let _ = writeln!(text, "media staging"); let _ = writeln!( text, " camera: {} | quality={} | enabled={}", self.selected_camera.as_deref().unwrap_or("auto"), self.camera_quality_label, self.media_channels.camera ); let _ = writeln!( text, " speaker: {} | volume={} | enabled={}", self.selected_speaker.as_deref().unwrap_or("auto"), self.audio_gain_label, self.media_channels.audio ); let _ = writeln!( text, " microphone: {} | gain={} | enabled={}", self.selected_microphone.as_deref().unwrap_or("auto"), self.mic_gain_label, self.media_channels.microphone ); let _ = writeln!( text, " uplink camera: {}", uplink_summary(&self.upstream_camera) ); let _ = writeln!( text, " uplink microphone: {}", uplink_summary(&self.upstream_microphone) ); let _ = writeln!( text, " keyboard: {}", self.selected_keyboard.as_deref().unwrap_or("all") ); let _ = writeln!( text, " mouse: {}", self.selected_mouse.as_deref().unwrap_or("all") ); let _ = writeln!(text); let _ = writeln!(text, "current UI state"); let _ = writeln!(text, " {}", self.status); let _ = writeln!(text); let _ = writeln!(text, "recent samples"); if self.recent_samples.is_empty() { let _ = writeln!( text, " no live RTT/probe-spread/loss samples yet; this report is currently a launcher state snapshot." ); } else { for sample in &self.recent_samples { let _ = writeln!( text, " rtt={:.1}ms probe-spread={:.1}ms input-floor={:.1}ms cpu={:.1}/{:.1}% probe-loss={:.1}% video-loss={:.1}% left={:.1}/{:.1}/{:.1}fps right={:.1}/{:.1}/{:.1}fps dropped={} queue={}/{} peaks=l{:.0}/{:.0}ms r{:.0}/{:.0}ms server=l{}:{:.0}/{:.0}/{} r{}:{:.0}/{:.0}/{}", sample.rtt_ms, sample.probe_spread_ms, sample.input_latency_ms, sample.client_process_cpu_pct, sample.server_process_cpu_pct, sample.probe_loss_pct, sample.video_loss_pct, sample.left_receive_fps, sample.left_present_fps, sample.left_server_fps, sample.right_receive_fps, sample.right_present_fps, sample.right_server_fps, sample.dropped_frames, sample.queue_depth, sample.left_queue_peak.max(sample.right_queue_peak), sample.left_packet_gap_peak_ms, sample.left_present_gap_peak_ms, sample.right_packet_gap_peak_ms, sample.right_present_gap_peak_ms, sample.left_server_encoder_label, sample.left_server_source_gap_peak_ms, sample.left_server_send_gap_peak_ms, sample.left_server_queue_peak, sample.right_server_encoder_label, sample.right_server_source_gap_peak_ms, sample.right_server_send_gap_peak_ms, sample.right_server_queue_peak ); let _ = writeln!( text, " uplink: cam={} mic={}", uplink_summary(&sample.upstream_camera), uplink_summary(&sample.upstream_microphone) ); } } let _ = writeln!(text); let _ = writeln!(text, "recommendations"); for item in &self.recommendations { let _ = writeln!(text, " - {item}"); } if !self.notes.is_empty() { let _ = writeln!(text); let _ = writeln!(text, "notes"); for item in &self.notes { let _ = writeln!(text, " - {item}"); } } let _ = writeln!(text); let _ = writeln!(text, "quality probe"); let _ = writeln!(text, " {}", self.probe_command); text } } fn uplink_summary(stream: &crate::uplink_telemetry::UpstreamStreamTelemetry) -> String { if !stream.enabled { return "disabled".to_string(); } let connection = if stream.connected { "live" } else if stream.reconnect_count > 0 { "reconnecting" } else { "idle" }; let error = if stream.last_error.is_empty() { "ok".to_string() } else { stream.last_error.clone() }; format!( "{connection} queue={}/{} enq-age={:.0}/{:.0}ms delivery={:.0}/{:.0}ms block-peak={:.0}ms reconnects={} streamed={} drops(total/full/stale)={}/{}/{} error={error}", stream.queue_depth, stream.queue_peak, stream.latest_enqueue_age_ms, stream.enqueue_age_peak_ms, stream.latest_delivery_age_ms, stream.delivery_age_peak_ms, stream.enqueue_block_peak_ms, stream.reconnect_count, stream.packets_streamed, stream.dropped_packets, stream.dropped_queue_full_packets, stream.dropped_stale_packets ) }