Skip to main content

parallax_diagnostics/
lib.rs

1//! Structured tracing and host diagnostics for Parallax.
2
3#![deny(unsafe_code)]
4#![warn(missing_docs)]
5
6use parallax_core::{ComponentVersions, RuntimeKind, RuntimeStatus};
7use serde::{Deserialize, Serialize};
8use tracing_subscriber::{fmt, EnvFilter};
9
10/// Initialize tracing from `RUST_LOG` / explicit filter.
11pub 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/// Single runtime health check result.
31#[derive(Clone, Debug, Serialize, Deserialize)]
32pub struct RuntimeHealth {
33    /// Runtime kind.
34    pub runtime: RuntimeKind,
35    /// Status.
36    pub status: RuntimeStatus,
37    /// Detected binary/path if any.
38    pub binary: Option<String>,
39    /// Version string if probed.
40    pub version: Option<String>,
41    /// Detail / error text.
42    pub detail: Option<String>,
43}
44
45/// Full `plx doctor` report.
46#[derive(Clone, Debug, Serialize, Deserialize)]
47pub struct DoctorReport {
48    /// Parallax version.
49    pub parallax_version: String,
50    /// Independently bumpable component versions.
51    #[serde(default = "ComponentVersions::current")]
52    pub versions: ComponentVersions,
53    /// Host OS description.
54    pub host: String,
55    /// Per-runtime health.
56    pub runtimes: Vec<RuntimeHealth>,
57    /// Overall ok when at least one executable runtime is Ready.
58    pub ok: bool,
59}
60
61impl DoctorReport {
62    /// Format a human-readable report.
63    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/// Trace event recorded during migrate/run for `--trace` / JSON output.
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct TraceEvent {
104    /// Monotonic order.
105    pub seq: u64,
106    /// Phase name.
107    pub phase: String,
108    /// Duration microseconds for this phase (if completed).
109    pub duration_us: Option<u64>,
110    /// Optional message.
111    pub message: Option<String>,
112}
113
114/// Simple in-memory trace buffer.
115#[derive(Clone, Debug, Default)]
116pub struct TraceBuffer {
117    events: Vec<TraceEvent>,
118}
119
120impl TraceBuffer {
121    /// Create empty buffer.
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    /// Push an event.
127    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    /// Borrow events.
143    pub fn events(&self) -> &[TraceEvent] {
144        &self.events
145    }
146}