1use crate::model::clock;
2use crate::model::errors::LbResult;
3use crate::{Lb, get_code_version};
4use basic_human_duration::ChronoHumanDuration;
5use chrono::NaiveDateTime;
6use serde::Serialize;
7use std::env;
8use std::path::Path;
9use std::sync::atomic::Ordering;
10use time::Duration;
11use tokio::fs::{self};
12
13#[derive(Serialize)]
14pub struct DebugInfo {
15 pub time: String,
16 pub name: String,
17 pub last_synced: String,
18 pub lb_version: String,
19 pub rust_triple: String,
20 pub os_info: String,
21 pub lb_dir: String,
22 pub server_url: String,
23 pub integrity: String,
24 pub is_syncing: bool,
25 pub status: String,
26 pub panics: Vec<String>,
27}
28
29impl Lb {
30 async fn human_last_synced(&self) -> String {
31 let tx = self.ro_tx().await;
32 let db = tx.db();
33
34 let last_synced = *db.last_synced.get().unwrap_or(&0);
35
36 if last_synced != 0 {
37 Duration::milliseconds(clock::get_time().0 - last_synced)
38 .format_human()
39 .to_string()
40 } else {
41 "never".to_string()
42 }
43 }
44
45 fn now(&self) -> String {
46 let now = chrono::Local::now();
47 now.format("%Y-%m-%d %H:%M:%S %Z").to_string()
48 }
49
50 async fn collect_panics(&self) -> LbResult<Vec<String>> {
51 let mut panics = vec![];
52
53 let dir_path = &self.config.writeable_path;
54 let path = Path::new(dir_path);
55
56 let prefix = "panic---";
57 let suffix = ".log";
58 let timestamp_format = "%Y-%m-%d---%H-%M-%S";
59
60 let mut entries = fs::read_dir(path).await?;
61 while let Some(entry) = entries.next_entry().await? {
62 let file_name = entry.file_name().into_string().unwrap_or_default();
63
64 if file_name.starts_with(prefix) && file_name.ends_with(suffix) {
66 let timestamp_str = &file_name[prefix.len()..file_name.len() - suffix.len()];
68
69 if let Ok(timestamp) =
71 NaiveDateTime::parse_from_str(timestamp_str, timestamp_format)
72 {
73 let file_path = path.join(file_name);
74 let contents = fs::read_to_string(file_path).await?;
75 let contents = format!("time: {timestamp}: contents: {contents}");
76 panics.push(contents);
77 }
78 }
79 }
80 Ok(panics)
81 }
82
83 #[instrument(level = "debug", skip(self), err(Debug))]
84 pub async fn debug_info(&self, os_info: String) -> LbResult<String> {
85 let account = self.get_account()?;
86
87 let arch = env::consts::ARCH;
88 let os = env::consts::OS;
89 let family = env::consts::FAMILY;
90
91 let (integrity, last_synced, panics) = tokio::join!(
92 self.test_repo_integrity(),
93 self.human_last_synced(),
94 self.collect_panics()
95 );
96
97 let mut status = self.status().await;
98 status.space_used = None;
99 let status = format!("{status:?}");
100 let is_syncing = self.syncing.load(Ordering::Relaxed);
101
102 Ok(serde_json::to_string_pretty(&DebugInfo {
103 time: self.now(),
104 name: account.username.clone(),
105 lb_version: get_code_version().into(),
106 rust_triple: format!("{arch}.{family}.{os}"),
107 server_url: account.api_url.clone(),
108 integrity: format!("{integrity:?}"),
109 lb_dir: self.config.writeable_path.clone(),
110 last_synced,
111 os_info,
112 status,
113 is_syncing,
114 panics: panics?,
115 })?)
116 }
117}