From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 9871B1FF178 for ; Mon, 1 Dec 2025 13:59:14 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 91EE11BF86; Mon, 1 Dec 2025 13:59:36 +0100 (CET) From: Lukas Wagner To: pdm-devel@lists.proxmox.com Date: Mon, 1 Dec 2025 13:58:55 +0100 Message-ID: <20251201125859.237091-2-l.wagner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20251201125859.237091-1-l.wagner@proxmox.com> References: <20251201125859.237091-1-l.wagner@proxmox.com> MIME-Version: 1.0 X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1764593898306 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.032 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: [pdm-devel] [PATCH datacenter-manager 1/4] server: add system report implementation X-BeenThere: pdm-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Datacenter Manager development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox Datacenter Manager development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pdm-devel-bounces@lists.proxmox.com Sender: "pdm-devel" This code was taken from PBS and then adapted for PDM. While it could make sense to refactor and then share some of the helper functions with PBS, the benefit is rather small (the helpers are relatively trivial) and now is not the best time for it. The risk and potential consequences of diverging implementations is small. Signed-off-by: Lukas Wagner --- server/src/lib.rs | 1 + server/src/report.rs | 195 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 server/src/report.rs diff --git a/server/src/lib.rs b/server/src/lib.rs index bd8660a7..5ed10d69 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -11,6 +11,7 @@ pub mod parallel_fetcher; pub mod remote_cache; pub mod remote_tasks; pub mod remote_updates; +pub mod report; pub mod resource_cache; pub mod task_utils; pub mod views; diff --git a/server/src/report.rs b/server/src/report.rs new file mode 100644 index 00000000..247db4f1 --- /dev/null +++ b/server/src/report.rs @@ -0,0 +1,195 @@ +use std::fmt::Write; +use std::path::Path; +use std::process::Command; + +// TODO: This was copied from PBS. Might make sense to refactor these a little +// bit and move them a `proxmox-system-report` crate or something. + +fn get_top_processes() -> String { + let (exe, args) = ("top", vec!["-b", "-c", "-w512", "-n", "1", "-o", "TIME"]); + let output = Command::new(exe).args(&args).output(); + let output = match output { + Ok(output) => String::from_utf8_lossy(&output.stdout).to_string(), + Err(err) => err.to_string(), + }; + let output = output.lines().take(30).collect::>().join("\n"); + format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" ")) +} + +fn files() -> Vec<(&'static str, Vec<&'static str>)> { + vec![ + ( + "General System Info", + vec![ + "/etc/hostname", + "/etc/hosts", + "/etc/network/interfaces", + "/etc/apt/sources.list", + "/etc/apt/sources.list.d/", + "/proc/pressure/", + ], + ), + ( + "User & Access", + vec![ + "/etc/proxmox-datacenter-manager/access/user.cfg", + "/etc/proxmox-datacenter-manager/access/acl.cfg", + ], + ), + ( + "Others", + vec![ + "/etc/proxmox-datacenter-manager/node.cfg", + "/etc/proxmox-datacenter-manager/views.cfg", + ], + ), + ] +} + +fn commands() -> Vec<(&'static str, Vec<&'static str>)> { + vec![ + // ("", vec![]) + ("date", vec!["-R"]), + ( + "proxmox-datacenter-manager-admin", + vec!["versions", "--verbose"], + ), + ("proxmox-datacenter-manager-admin", vec!["remote", "list"]), + // FIXME: Does not exist yet. + // ("proxmox-datacenter-manager-admin", vec!["subscription", "get"]), + ("proxmox-boot-tool", vec!["status"]), + ("df", vec!["-h"]), + ( + "lsblk", + vec![ + "--ascii", + "-M", + "-o", + "+HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN", + ], + ), + ("ls", vec!["-l", "/dev/disk/by-id", "/dev/disk/by-path"]), + ("zpool", vec!["status"]), + ("zfs", vec!["list"]), + ("arcstat", vec![]), + ] +} + +// (description, function()) +type FunctionMapping = (&'static str, fn() -> String); + +fn function_calls() -> Vec { + vec![("System Load & Uptime", get_top_processes)] +} + +fn get_file_content(file: impl AsRef) -> String { + use proxmox_sys::fs::file_read_optional_string; + let content = match file_read_optional_string(&file) { + Ok(Some(content)) => content, + Ok(None) => String::from("# file does not exist"), + Err(err) => err.to_string(), + }; + let file_name = file.as_ref().display(); + format!("`$ cat '{file_name}'`\n```\n{}\n```", content.trim_end()) +} + +fn get_directory_content(path: impl AsRef) -> String { + let read_dir_iter = match std::fs::read_dir(&path) { + Ok(iter) => iter, + Err(err) => { + return format!( + "`$ cat '{}*'`\n```\n# read dir failed - {err}\n```", + path.as_ref().display(), + ); + } + }; + let mut out = String::new(); + let mut first = true; + for entry in read_dir_iter { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + let _ = writeln!(out, "error during read-dir - {err}"); + continue; + } + }; + let path = entry.path(); + if path.is_file() { + if first { + let _ = writeln!(out, "{}", get_file_content(path)); + first = false; + } else { + let _ = writeln!(out, "\n{}", get_file_content(path)); + } + } else { + let _ = writeln!(out, "skipping sub-directory `{}`", path.display()); + } + } + out +} + +fn get_command_output(exe: &str, args: &Vec<&str>) -> String { + let output = Command::new(exe) + .env("PROXMOX_OUTPUT_NO_BORDER", "1") + .args(args) + .output(); + let output = match output { + Ok(output) => { + let mut out = String::from_utf8_lossy(&output.stdout) + .trim_end() + .to_string(); + let stderr = String::from_utf8_lossy(&output.stderr) + .trim_end() + .to_string(); + if !stderr.is_empty() { + let _ = writeln!(out, "\n```\nSTDERR:\n```\n{stderr}"); + } + out + } + Err(err) => err.to_string(), + }; + format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" ")) +} + +pub fn generate_report() -> String { + let file_contents = files() + .iter() + .map(|group| { + let (group, files) = group; + let group_content = files + .iter() + .map(|file_name| { + let path = Path::new(file_name); + if path.is_dir() { + get_directory_content(path) + } else { + get_file_content(file_name) + } + }) + .collect::>() + .join("\n\n"); + + format!("### {group}\n\n{group_content}") + }) + .collect::>() + .join("\n\n"); + + let command_outputs = commands() + .iter() + .map(|(command, args)| get_command_output(command, args)) + .collect::>() + .join("\n\n"); + + let function_outputs = function_calls() + .iter() + .map(|(desc, function)| { + let output = function(); + format!("#### {desc}\n{}\n", output.trim_end()) + }) + .collect::>() + .join("\n\n"); + + format!( + "## FILES\n\n{file_contents}\n## COMMANDS\n\n{command_outputs}\n## FUNCTIONS\n\n{function_outputs}\n" + ) +} -- 2.47.3 _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel