1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct Record {
7 pub v: u32,
8 pub id: String,
9 pub host: String,
10 pub pid: u32,
11 pub pid_start: u64,
12 pub tool: String,
13 #[serde(default)]
14 pub app: Option<String>,
15 pub label: String,
16 #[serde(default)]
17 pub detail: Option<String>,
18 #[serde(default)]
19 pub invoker: Option<String>,
20 #[serde(default)]
21 pub parent: Option<String>,
22 pub state: String,
23 #[serde(default)]
24 pub stage: Option<String>,
25 #[serde(default, skip_serializing_if = "Vec::is_empty")]
26 pub stages: Vec<String>,
27 #[serde(default)]
28 pub stage_note: Option<String>,
29 #[serde(default)]
30 pub progress: Option<Progress>,
31 #[serde(default)]
32 pub rate: Option<f64>,
33 #[serde(default)]
34 pub pgpu: Option<Pgpu>,
35 #[serde(default)]
36 pub device: Option<String>,
37 #[serde(default)]
38 pub out: Option<String>,
39 pub started: u64,
40 pub updated: u64,
41 #[serde(default)]
42 pub ended: Option<u64>,
43 #[serde(default)]
44 pub exit: Option<i32>,
45 #[serde(default)]
46 pub error: Option<String>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct Progress {
51 pub done: u64,
52 #[serde(default)]
53 pub total: Option<u64>,
54 pub unit: String,
55}
56
57impl Progress {
58 pub fn fraction(&self) -> Option<f64> {
59 let total = self.total.filter(|total| *total > 0)?;
60 crate::op::clean(Some(self.done as f64 / total as f64))
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct Pgpu {
66 pub pid: u32,
67 #[serde(rename = "as")]
68 pub as_name: String,
69}
70
71pub fn read_dir(path: impl AsRef<Path>) -> Vec<Record> {
72 let Ok(entries) = std::fs::read_dir(path) else {
73 return Vec::new();
74 };
75 let mut found = Vec::new();
76 for entry in entries.flatten() {
77 let name = entry.file_name();
78 if name.to_string_lossy().starts_with('.') {
79 continue;
80 }
81 let Ok(meta) = entry.metadata() else {
82 continue;
83 };
84 if !meta.is_file() {
85 continue;
86 }
87 let Some(bytes) = crate::util::read_capped(&entry.path()) else {
88 continue;
89 };
90 if let Ok(record) = serde_json::from_slice::<Record>(&bytes) {
91 found.push((name, record));
92 }
93 }
94 found.sort_by(|left, right| left.0.cmp(&right.0));
95 found.into_iter().map(|(_, record)| record).collect()
96}