public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christian Ebner <c.ebner@proxmox.com>
To: Erik Fastermann <e.fastermann@proxmox.com>,
	pbs-devel@lists.proxmox.com, pdm-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox-backup 2/3] report: use shared proxmox-system-report crate
Date: Tue, 28 Jul 2026 15:59:45 +0200	[thread overview]
Message-ID: <c7c9954e-fc4f-46e4-b8db-1d139f097dba@proxmox.com> (raw)
In-Reply-To: <20260714083156.99794-3-e.fastermann@proxmox.com>

On 7/14/26 10:31 AM, Erik Fastermann wrote:
> Move the common report logic to the proxmox-system-report crate and
> keep only the PBS specific files, commands and the Datastores
> function here.
> 
> User-visible output changes:
> - df now also shows the filesystem type (df -h -T), matching PDM
> - adds the IPv4 and IPv6 route tables (ip -4/-6 route show),
>    previously PDM only
> 
> The section order (FILES, COMMANDS, FUNCTIONS) and all product
> specific entries are 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           |   2 +
>   src/server/report.rs | 213 +++----------------------------------------
>   2 files changed, 15 insertions(+), 200 deletions(-)
> 
> diff --git a/Cargo.toml b/Cargo.toml
> index a625370cf..fb7904d38 100644
> --- a/Cargo.toml
> +++ b/Cargo.toml
> @@ -94,6 +94,7 @@ proxmox-sortable-macro = "1"
>   proxmox-subscription = { version = "1.0.2", features = [ "api-types" ] }
>   proxmox-syslog-api = { version = "1.1", features = [ "impl" ] }
>   proxmox-sys = "1"
> +proxmox-system-report = "0.1"

nit: as motivated in patch 1, lets start with 1.0 here.... also, this 
requires a build dependency in debian/control on the corresponding dev 
package, so it is pulled in by apt.

>   proxmox-systemd = "1.0.1"
>   proxmox-tfa = { version = "6.0.3", features = [ "api", "api-types" ] }
>   proxmox-time = "2"
> @@ -252,6 +253,7 @@ proxmox-sortable-macro.workspace = true
>   proxmox-subscription.workspace = true
>   proxmox-syslog-api.workspace = true
>   proxmox-sys = { workspace = true, features = [ "timer" ] }
> +proxmox-system-report.workspace = true
>   proxmox-systemd.workspace = true
>   proxmox-tfa.workspace = true
>   proxmox-time.workspace = true
> diff --git a/src/server/report.rs b/src/server/report.rs
> index 297718d2e..442a8573d 100644
> --- a/src/server/report.rs
> +++ b/src/server/report.rs
> @@ -1,33 +1,7 @@
> -use std::fmt::Write;
> -use std::path::Path;
> -use std::process::Command;
> +use proxmox_system_report::{CommandSpec, FileGroup, FunctionMapping};
>   
> -use proxmox_network_api::NetworkInterfaceType;
> -
> -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> {

nit: maybe better call this pbs_specific_files() or 
product_specific_files(), just to make it clearer that this is only part 
of what's to be generated.

>       vec![
> -        (
> -            "General System Info",
> -            vec![
> -                "/etc/hostname",
> -                "/etc/hosts",
> -                "/etc/network/interfaces",
> -                "/etc/apt/sources.list",
> -                "/etc/apt/sources.list.d/",
> -                "/proc/pressure/",
> -            ],
> -        ),
>           (
>               "Datastores & Remotes",
>               vec!["/etc/proxmox-backup/datastore.cfg"],
> @@ -65,192 +39,31 @@ fn files() -> Vec<(&'static str, Vec<&'static str>)> {
>       ]
>   }
>   
> -fn commands() -> Vec<(&'static str, Vec<&'static str>)> {
> +fn commands() -> Vec<CommandSpec> {

nit: same as above for files()

>       vec![
> -        //  ("<command>", vec![<arg [, arg]>])
>           ("date", vec!["-R"]),
>           ("proxmox-backup-manager", vec!["versions", "--verbose"]),
>           ("proxmox-backup-manager", vec!["subscription", "get"]),
>           ("proxmox-backup-manager", vec!["ldap", "list"]),
>           ("proxmox-backup-manager", vec!["openid", "list"]),
> -        ("proxmox-boot-tool", vec!["status"]),
> -        ("df", vec!["-h"]),
> -        (
> -            "lsblk",
> -            vec![
> -                "--ascii",
> -                "-M",
> -                "-o",
> -                "+HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN",
> -            ],
> -        ),
> -        ("bash", vec!["-c", "ls -l /dev/disk/by-*/"]),
> -        ("zpool", vec!["status"]),
> -        ("zfs", vec!["list"]),
> -        ("zarcstat", vec![]),
> -        ("dmidecode", vec!["-t", "bios"]),
> -        ("lscpu", vec![]),
> -        ("lspci", vec!["-nnk"]),
> -        ("ip", vec!["-details", "-statistics", "a"]),
>       ]
>   }
>   
> -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> {

nit: same as above for files() and commands()...

> -    vec![
> -        ("Datastores", || {
> -            let config = match pbs_config::datastore::config() {
> -                Ok((config, _digest)) => config,
> -                _ => return String::from("could not read datastore config"),
> -            };
> -
> -            let mut list = Vec::new();
> -            for store in config.sections.keys() {
> -                list.push(store.as_str());
> -            }
> -            format!("```\n{}\n```", list.join(", "))
> -        }),
> -        ("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;
> -            }
> +    vec![("Datastores", || {
> +        let config = match pbs_config::datastore::config() {
> +            Ok((config, _digest)) => config,
> +            _ => return String::from("could not read datastore config"),
>           };
> -        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
> +        let mut list = Vec::new();
> +        for store in config.sections.keys() {
> +            list.push(store.as_str());
>           }
> -        Err(err) => err.to_string(),
> -    };
> -    format!("$ `{exe} {}`\n```\n{output}\n```", args.join(" "))
> +        format!("```\n{}\n```", list.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!(
> -        "## FILES\n\n{file_contents}\n## COMMANDS\n\n{command_outputs}\n## FUNCTIONS\n\n{function_outputs}\n"
> -    )
> +    proxmox_system_report::generate_report(files(), commands(), function_calls())
>   }





  reply	other threads:[~2026-07-28 13:59 UTC|newest]

Thread overview: 12+ 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-28 13:59   ` Christian Ebner
2026-07-29 11:55     ` Erik Fastermann
2026-07-29 12:08       ` Christian Ebner
2026-07-14  8:31 ` [PATCH proxmox-backup 2/3] report: use shared proxmox-system-report crate Erik Fastermann
2026-07-28 13:59   ` Christian Ebner [this message]
2026-07-14  8:31 ` [PATCH proxmox-datacenter-manager 3/3] " Erik Fastermann
2026-07-28 13:59   ` Christian Ebner
2026-07-14  9:05 ` [PATCH proxmox{,-backup,-datacenter-manager} 0/3] factor system report into shared crate Erik Fastermann
2026-07-28 13:59 ` Christian Ebner
2026-07-30 11:14 ` superseded: " 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=c7c9954e-fc4f-46e4-b8db-1d139f097dba@proxmox.com \
    --to=c.ebner@proxmox.com \
    --cc=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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal