parallax_diagnostics/
lib.rs1#![deny(unsafe_code)]
4#![warn(missing_docs)]
5
6use parallax_core::{ComponentVersions, RuntimeKind, RuntimeStatus};
7use serde::{Deserialize, Serialize};
8use tracing_subscriber::{fmt, EnvFilter};
9
10pub fn init_tracing(json: bool, verbose: bool) {
12 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
13 if verbose {
14 EnvFilter::new("info,parallax=debug")
15 } else {
16 EnvFilter::new("warn,parallax=info")
17 }
18 });
19 let subscriber = fmt::Subscriber::builder()
20 .with_env_filter(filter)
21 .with_target(true)
22 .with_writer(std::io::stderr);
23 if json {
24 let _ = tracing::subscriber::set_global_default(subscriber.json().finish());
25 } else {
26 let _ = tracing::subscriber::set_global_default(subscriber.finish());
27 }
28}
29
30#[derive(Clone, Debug, Serialize, Deserialize)]
32pub struct RuntimeHealth {
33 pub runtime: RuntimeKind,
35 pub status: RuntimeStatus,
37 pub binary: Option<String>,
39 pub version: Option<String>,
41 pub detail: Option<String>,
43}
44
45#[derive(Clone, Debug, Serialize, Deserialize)]
47pub struct DoctorReport {
48 pub parallax_version: String,
50 #[serde(default = "ComponentVersions::current")]
52 pub versions: ComponentVersions,
53 pub host: String,
55 pub runtimes: Vec<RuntimeHealth>,
57 pub ok: bool,
59}
60
61impl DoctorReport {
62 pub fn format_human(&self) -> String {
64 let mut out = String::new();
65 out.push_str(&format!("Parallax {}\n", self.parallax_version));
66 out.push_str(&format!(
67 "Components: pir={} protocol={} snapshot={} adapter_interface={}\n",
68 self.versions.pir_schema,
69 self.versions.protocol,
70 self.versions.snapshot,
71 self.versions.adapter_interface
72 ));
73 out.push_str(&format!("Host: {}\n\n", self.host));
74 for r in &self.runtimes {
75 let status = match &r.status {
76 RuntimeStatus::Ready => "READY".to_string(),
77 RuntimeStatus::Degraded { reason } => format!("DEGRADED ({reason})"),
78 RuntimeStatus::Unavailable { reason } => format!("UNAVAILABLE ({reason})"),
79 };
80 out.push_str(&format!("[{}] {}\n", r.runtime, status));
81 if let Some(b) = &r.binary {
82 out.push_str(&format!(" binary: {b}\n"));
83 }
84 if let Some(v) = &r.version {
85 out.push_str(&format!(" version: {v}\n"));
86 }
87 if let Some(d) = &r.detail {
88 out.push_str(&format!(" detail: {d}\n"));
89 }
90 out.push('\n');
91 }
92 if self.ok {
93 out.push_str("Doctor: OK — at least one runtime is ready.\n");
94 } else {
95 out.push_str("Doctor: FAILED — no ready runtimes detected.\n");
96 }
97 out
98 }
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct TraceEvent {
104 pub seq: u64,
106 pub phase: String,
108 pub duration_us: Option<u64>,
110 pub message: Option<String>,
112}
113
114#[derive(Clone, Debug, Default)]
116pub struct TraceBuffer {
117 events: Vec<TraceEvent>,
118}
119
120impl TraceBuffer {
121 pub fn new() -> Self {
123 Self::default()
124 }
125
126 pub fn push(
128 &mut self,
129 phase: impl Into<String>,
130 duration_us: Option<u64>,
131 message: Option<String>,
132 ) {
133 let seq = self.events.len() as u64;
134 self.events.push(TraceEvent {
135 seq,
136 phase: phase.into(),
137 duration_us,
138 message,
139 });
140 }
141
142 pub fn events(&self) -> &[TraceEvent] {
144 &self.events
145 }
146}