From: Erik Fastermann <e.fastermann@proxmox.com>
To: pbs-devel@lists.proxmox.com, pdm-devel@lists.proxmox.com
Cc: Erik Fastermann <e.fastermann@proxmox.com>
Subject: [PATCH proxmox-datacenter-manager 3/3] report: use shared proxmox-system-report crate
Date: Tue, 14 Jul 2026 10:31:56 +0200 [thread overview]
Message-ID: <20260714083156.99794-4-e.fastermann@proxmox.com> (raw)
In-Reply-To: <20260714083156.99794-1-e.fastermann@proxmox.com>
Move the common report logic to the proxmox-system-report crate and
keep only the PDM specific files and commands here.
User-visible output changes:
- section order is now FILES, COMMANDS, FUNCTIONS, matching PBS
(was COMMANDS, FUNCTIONS, FILES)
- adds dmidecode, lscpu and lspci output, previously PBS only
- the disk listing now covers all /dev/disk/by-* directories instead
of only by-id and by-path, matching PBS
Product specific entries and their contents are otherwise unchanged.
Suggested-by: Lukas Wagner <l.wagner@proxmox.com>
Suggested-by: Christian Ebner <c.ebner@proxmox.com>
Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com>
---
Cargo.toml | 1 +
server/Cargo.toml | 1 +
server/src/report.rs | 198 +------------------------------------------
3 files changed, 6 insertions(+), 194 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index ee96ae14..d68c78e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -63,6 +63,7 @@ proxmox-simple-config = "1"
proxmox-sortable-macro = "1"
proxmox-subscription = { version = "1.0.2", features = [ "api-types"], default-features = false }
proxmox-sys = "1"
+proxmox-system-report = "0.1"
proxmox-systemd = "1"
proxmox-tfa = { version = "6", features = [ "api-types" ], default-features = false }
proxmox-time = "2"
diff --git a/server/Cargo.toml b/server/Cargo.toml
index 663f21cb..e8b21d3c 100644
--- a/server/Cargo.toml
+++ b/server/Cargo.toml
@@ -61,6 +61,7 @@ proxmox-serde = { workspace = true, features = [ "serde_json" ] }
proxmox-sortable-macro.workspace = true
proxmox-subscription = { workspace = true, features = [ "api-types", "impl" ] }
proxmox-sys = { workspace = true, features = [ "timer" ] }
+proxmox-system-report.workspace = true
proxmox-systemd.workspace = true
proxmox-tfa = { workspace = true, features = [ "api" ] }
proxmox-time.workspace = true
diff --git a/server/src/report.rs b/server/src/report.rs
index 3c87b991..585df35a 100644
--- a/server/src/report.rs
+++ b/server/src/report.rs
@@ -1,36 +1,7 @@
-use std::fmt::Write;
-use std::path::Path;
-use std::process::Command;
+use proxmox_system_report::{CommandSpec, FileGroup};
-use proxmox_network_api::NetworkInterfaceType;
-
-// 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::<Vec<&str>>().join("\n");
- format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" "))
-}
-
-fn files() -> Vec<(&'static str, Vec<&'static str>)> {
+fn files() -> Vec<FileGroup> {
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![
@@ -48,9 +19,8 @@ fn files() -> Vec<(&'static str, Vec<&'static str>)> {
]
}
-fn commands() -> Vec<(&'static str, Vec<&'static str>)> {
+fn commands() -> Vec<CommandSpec> {
vec![
- // ("<command>", vec![<arg [, arg]>])
("date", vec!["-R"]),
(
"proxmox-datacenter-manager-admin",
@@ -65,169 +35,9 @@ fn commands() -> Vec<(&'static str, Vec<&'static str>)> {
"proxmox-datacenter-manager-admin",
vec!["support-status", "get"],
),
- ("proxmox-boot-tool", vec!["status"]),
- ("df", vec!["-h", "-T"]),
- (
- "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"]),
- ("zarcstat", vec![]),
- ("ip", vec!["-details", "-statistics", "address"]),
- ("ip", vec!["-4", "route", "show"]),
- ("ip", vec!["-6", "route", "show"]),
]
}
-fn dynamic_commands() -> Vec<(&'static str, Vec<String>)> {
- let mut commands = Vec::new();
-
- match proxmox_network_api::config() {
- Ok((config, _)) => {
- for (name, iface) in config.interfaces {
- if iface.interface_type == NetworkInterfaceType::Eth {
- commands.push(("ethtool", vec![name]));
- }
- }
- }
- Err(err) => {
- eprintln!("failed to query network interfaces: {err}");
- }
- }
-
- commands
-}
-
-// (description, function())
-type FunctionMapping = (&'static str, fn() -> String);
-
-fn function_calls() -> Vec<FunctionMapping> {
- vec![("System Load & Uptime", get_top_processes)]
-}
-
-fn get_file_content(file: impl AsRef<Path>) -> 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<Path>) -> 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::<Vec<String>>()
- .join("\n\n");
-
- format!("### {group}\n\n{group_content}")
- })
- .collect::<Vec<String>>()
- .join("\n\n");
-
- let static_command_outputs = commands()
- .into_iter()
- .map(|(command, args)| get_command_output(command, &args));
-
- let dynamic_command_outputs = dynamic_commands().into_iter().map(|(command, args)| {
- let args = args.iter().map(String::as_str).collect();
- get_command_output(command, &args)
- });
-
- let command_outputs = static_command_outputs
- .chain(dynamic_command_outputs)
- .collect::<Vec<String>>()
- .join("\n\n");
-
- let function_outputs = function_calls()
- .iter()
- .map(|(desc, function)| {
- let output = function();
- format!("#### {desc}\n{}\n", output.trim_end())
- })
- .collect::<Vec<String>>()
- .join("\n\n");
-
- format!(
- "## COMMANDS \n\n {command_outputs}\n\n## FUNCTIONS\n\n{function_outputs}\n## FILES\n\n{file_contents}\n"
- )
+ proxmox_system_report::generate_report(files(), commands(), vec![])
}
--
2.47.3
next prev parent reply other threads:[~2026-07-14 8:32 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-14 8:31 [PATCH proxmox{,-backup,-datacenter-manager} 0/3] factor system report into shared crate Erik Fastermann
2026-07-14 8:31 ` [PATCH proxmox 1/3] system-report: add crate for shared report generation Erik Fastermann
2026-07-14 8:31 ` [PATCH proxmox-backup 2/3] report: use shared proxmox-system-report crate Erik Fastermann
2026-07-14 8:31 ` Erik Fastermann [this message]
2026-07-14 9:05 ` [PATCH proxmox{,-backup,-datacenter-manager} 0/3] factor system report into shared crate Erik Fastermann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260714083156.99794-4-e.fastermann@proxmox.com \
--to=e.fastermann@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
--cc=pdm-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox