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 429FF1FF178 for ; Mon, 1 Dec 2025 16:06:49 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id B38E02053A; Mon, 1 Dec 2025 16:07:13 +0100 (CET) Mime-Version: 1.0 Date: Mon, 01 Dec 2025 16:06:39 +0100 Message-Id: To: "Shannon Sterz" , "Lukas Wagner" From: "Lukas Wagner" X-Mailer: aerc 0.21.0-0-g5549850facc2-dirty References: <20251201125859.237091-1-l.wagner@proxmox.com> <20251201125859.237091-2-l.wagner@proxmox.com> In-Reply-To: X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1764601556333 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.030 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 RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: Re: [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 Cc: 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" On Mon Dec 1, 2025 at 3:55 PM CET, Shannon Sterz wrote: > On Mon Dec 1, 2025 at 1:58 PM CET, Lukas Wagner wrote: >> 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", > > hm the only thing here is that layout descriptions could become rather > long and i'm not sure how useful they are for support reasons (filters > more so). however, not too much of an issue imo. > >> + ], >> + ), >> + ] >> +} >> + >> +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)] >> +} > > a little confused why this gets its own category. `top` usually appears > more toward the top of our reports, this puts it on the very bottom. > also `get_top_processes` essentially also just calls a command, so not > sure why this is separated out. > This was just taken as-is from PBS, see [1]. I think it's called as a function rather than a command because we limit the output to the first 30 lines? [1] https://git.proxmox.com/?p=proxmox-backup.git;a=blob;f=src/server/report.rs;h=546555fb97ba65ae8a748e64aaf5869b6fc7dd2c;hb=HEAD#l99 >> + >> +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" >> + ) >> +} _______________________________________________ pdm-devel mailing list pdm-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel