public inbox for pdm-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Lukas Wagner <l.wagner@proxmox.com>
To: pdm-devel@lists.proxmox.com
Subject: [PATCH proxmox 11/26] procfs: add helpers for querying pressure stall information
Date: Thu, 12 Mar 2026 14:52:12 +0100	[thread overview]
Message-ID: <20260312135229.420729-12-l.wagner@proxmox.com> (raw)
In-Reply-To: <20260312135229.420729-1-l.wagner@proxmox.com>

This is put into a new crate, proxmox-procfs, since proxmox-sys is
already quite large and should be split in the future. The general idea
is that the contents of proxmox_sys::linux::procfs should be moved into
this new crate (potentially after some API cleanup) at some point.

Signed-off-by: Lukas Wagner <l.wagner@proxmox.com>
---
 Cargo.toml                          |   2 +
 proxmox-procfs/Cargo.toml           |  18 ++
 proxmox-procfs/debian/changelog     |   5 +
 proxmox-procfs/debian/control       |  50 +++++
 proxmox-procfs/debian/copyright     |  18 ++
 proxmox-procfs/debian/debcargo.toml |   7 +
 proxmox-procfs/src/lib.rs           |   1 +
 proxmox-procfs/src/pressure.rs      | 334 ++++++++++++++++++++++++++++
 8 files changed, 435 insertions(+)
 create mode 100644 proxmox-procfs/Cargo.toml
 create mode 100644 proxmox-procfs/debian/changelog
 create mode 100644 proxmox-procfs/debian/control
 create mode 100644 proxmox-procfs/debian/copyright
 create mode 100644 proxmox-procfs/debian/debcargo.toml
 create mode 100644 proxmox-procfs/src/lib.rs
 create mode 100644 proxmox-procfs/src/pressure.rs

diff --git a/Cargo.toml b/Cargo.toml
index 8f3886bd..47847048 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,6 +36,7 @@ members = [
     "proxmox-openid",
     "proxmox-parallel-handler",
     "proxmox-pgp",
+    "proxmox-procfs",
     "proxmox-product-config",
     "proxmox-rate-limiter",
     "proxmox-resource-scheduling",
@@ -171,6 +172,7 @@ proxmox-login = { version = "1.0.0", path = "proxmox-login" }
 proxmox-network-types = { version = "1.0.0", path = "proxmox-network-types" }
 proxmox-parallel-handler = { version = "1.0.0", path = "proxmox-parallel-handler" }
 proxmox-pgp = { version = "1.0.0", path = "proxmox-pgp" }
+proxmox-procfs = { version = "0.1.0", path = "proxmox-procfs" }
 proxmox-product-config = { version = "1.0.0", path = "proxmox-product-config" }
 proxmox-config-digest = { version = "1.0.0", path = "proxmox-config-digest" }
 proxmox-rate-limiter = { version = "1.0.0", path = "proxmox-rate-limiter" }
diff --git a/proxmox-procfs/Cargo.toml b/proxmox-procfs/Cargo.toml
new file mode 100644
index 00000000..3c0fe1dd
--- /dev/null
+++ b/proxmox-procfs/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "proxmox-procfs"
+description = "helpers for reading system information from /proc"
+version = "0.1.0"
+
+authors.workspace = true
+edition.workspace = true
+exclude.workspace = true
+homepage.workspace = true
+license.workspace = true
+repository.workspace = true
+
+[dependencies]
+serde = { workspace = true, optional = true, features = ["derive"] }
+thiserror.workspace = true
+
+[features]
+serde = ["dep:serde"]
diff --git a/proxmox-procfs/debian/changelog b/proxmox-procfs/debian/changelog
new file mode 100644
index 00000000..9eee8f0b
--- /dev/null
+++ b/proxmox-procfs/debian/changelog
@@ -0,0 +1,5 @@
+rust-proxmox-procfs (0.1.0-1) unstable; urgency=medium
+
+  * initial version
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 26 Feb 2026 15:54:07 +0100
diff --git a/proxmox-procfs/debian/control b/proxmox-procfs/debian/control
new file mode 100644
index 00000000..6c4b4798
--- /dev/null
+++ b/proxmox-procfs/debian/control
@@ -0,0 +1,50 @@
+Source: rust-proxmox-procfs
+Section: rust
+Priority: optional
+Build-Depends: debhelper-compat (= 13),
+ dh-sequence-cargo
+Build-Depends-Arch: cargo:native <!nocheck>,
+ rustc:native <!nocheck>,
+ libstd-rust-dev <!nocheck>,
+ librust-thiserror-2+default-dev <!nocheck>
+Maintainer: Proxmox Support Team <support@proxmox.com>
+Standards-Version: 4.7.2
+Vcs-Git: git://git.proxmox.com/git/proxmox.git
+Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
+Homepage: https://proxmox.com
+X-Cargo-Crate: proxmox-procfs
+
+Package: librust-proxmox-procfs-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-thiserror-2+default-dev
+Suggests:
+ librust-proxmox-procfs+serde-dev (= ${binary:Version})
+Provides:
+ librust-proxmox-procfs+default-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0+default-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1+default-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1.0-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1.0+default-dev (= ${binary:Version})
+Description: Helpers for reading system information from /proc - Rust source code
+ Source code for Debianized Rust crate "proxmox-procfs"
+
+Package: librust-proxmox-procfs+serde-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-proxmox-procfs-dev (= ${binary:Version}),
+ librust-serde-1+default-dev,
+ librust-serde-1+derive-dev
+Provides:
+ librust-proxmox-procfs-0+serde-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1+serde-dev (= ${binary:Version}),
+ librust-proxmox-procfs-0.1.0+serde-dev (= ${binary:Version})
+Description: Helpers for reading system information from /proc - feature "serde"
+ This metapackage enables feature "serde" for the Rust proxmox-procfs crate, by
+ pulling in any additional dependencies needed by that feature.
diff --git a/proxmox-procfs/debian/copyright b/proxmox-procfs/debian/copyright
new file mode 100644
index 00000000..01138fa0
--- /dev/null
+++ b/proxmox-procfs/debian/copyright
@@ -0,0 +1,18 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files:
+ *
+Copyright: 2026 Proxmox Server Solutions GmbH <support@proxmox.com>
+License: AGPL-3.0-or-later
+ This program is free software: you can redistribute it and/or modify it under
+ the terms of the GNU Affero General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option) any
+ later version.
+ .
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU Affero General Public License along
+ with this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/proxmox-procfs/debian/debcargo.toml b/proxmox-procfs/debian/debcargo.toml
new file mode 100644
index 00000000..b7864cdb
--- /dev/null
+++ b/proxmox-procfs/debian/debcargo.toml
@@ -0,0 +1,7 @@
+overlay = "."
+crate_src_path = ".."
+maintainer = "Proxmox Support Team <support@proxmox.com>"
+
+[source]
+vcs_git = "git://git.proxmox.com/git/proxmox.git"
+vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
diff --git a/proxmox-procfs/src/lib.rs b/proxmox-procfs/src/lib.rs
new file mode 100644
index 00000000..a5670f34
--- /dev/null
+++ b/proxmox-procfs/src/lib.rs
@@ -0,0 +1 @@
+pub mod pressure;
diff --git a/proxmox-procfs/src/pressure.rs b/proxmox-procfs/src/pressure.rs
new file mode 100644
index 00000000..452b3892
--- /dev/null
+++ b/proxmox-procfs/src/pressure.rs
@@ -0,0 +1,334 @@
+//! Utilities for reading [Pressure Stall Information][psi] for the system or cgroups.
+//!
+//! To read pressure data, refer to [`PressureData::read_system`] and [`PressureData::read_cgroup`].
+//! [`PressureData::read_file`] can be use for lower-level access, proving the path to the
+//! pressure file directly.
+//!
+//! # Examples
+//!
+//! Read system-wide CPU pressure:
+//!
+//! ```no_run
+//! use proxmox_procfs::pressure::{PressureData, Resource};
+//!
+//! let cpu = PressureData::read_system(Resource::Cpu).unwrap();
+//! println!("CPU some avg10: {:.2}%", cpu.some.average_10);
+//! ```
+//!
+//! Read cgroup-level memory pressure:
+//!
+//! ```no_run
+//! use proxmox_procfs::pressure::{PressureData, Resource};
+//!
+//! let mem = PressureData::read_cgroup("system.slice", Resource::Memory).unwrap();
+//! println!("mem some avg10: {:.2}%", mem.some.average_10);
+//! ```
+//!
+//! [psi]: https://docs.kernel.org/accounting/psi.html
+//!
+
+use std::ffi::OsStr;
+use std::fs::File;
+use std::io::{BufRead, BufReader, ErrorKind};
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+#[derive(thiserror::Error, Debug)]
+/// Error type for pressure-related errors.
+pub enum Error {
+    /// General IO error when reading the pressure stall information file.
+    #[error("could not read pressure stall info file: {0}")]
+    Io(#[from] std::io::Error),
+
+    /// Pressure stall info file does not exist.
+    /// This is a distinct error variant so that the caller can differentiate between a
+    /// disappeared cgroup (e.g. if the guest was stopped) and other kinds of IO errors
+    #[error("pressure stall info file does not exist: {0}")]
+    NotFound(PathBuf),
+
+    /// The contents of the pressure stall file are unexpected. Should not really happen,
+    /// hopefully.
+    #[error("unexpected pressure stall file format: {0}")]
+    InvalidFormat(String),
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Debug)]
+/// Pressure stall information data.
+pub struct PressureData {
+    /// At least some tasks were stalled on a given resource.
+    pub some: PressureRecord,
+    /// All non-idle tasks were stalled on a given resource.
+    ///
+    /// Note: When querying CPU pressure stall information on a system level,
+    /// all members in `full` contain 0 (see [here]).
+    ///
+    /// [here]: https://docs.kernel.org/accounting/psi.html#pressure-interface
+    pub full: PressureRecord,
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
+#[derive(Clone, Debug)]
+/// Individual record corresponding to one line from a pressure stall information file.
+pub struct PressureRecord {
+    /// Average pressure stall ratio over the last 10 seconds.
+    pub average_10: f64,
+    /// Average pressure stall ratio over the last 60 seconds.
+    pub average_60: f64,
+    /// Average pressure stall ratio over the last 300 seconds.
+    pub average_300: f64,
+    /// Total stall time in microseconds.
+    pub total: u64,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum PressureRecordKind {
+    Full,
+    Some,
+}
+
+impl FromStr for PressureRecordKind {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "some" => Ok(Self::Some),
+            "full" => Ok(Self::Full),
+            _ => Err(Error::InvalidFormat(format!("invalid pressure kind '{s}'"))),
+        }
+    }
+}
+
+#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Copy, Debug, PartialEq)]
+/// Which pressure stall information to query.
+pub enum Resource {
+    /// Query CPU pressure stall information.
+    Cpu,
+    /// Query memory pressure stall information.
+    Memory,
+    /// Query IO pressure stall information.
+    Io,
+}
+
+impl Resource {
+    fn into_proc_path(self) -> &'static Path {
+        match self {
+            Resource::Cpu => Path::new("/proc/pressure/cpu"),
+            Resource::Memory => Path::new("/proc/pressure/memory"),
+            Resource::Io => Path::new("/proc/pressure/io"),
+        }
+    }
+
+    fn into_cgroup_path_component(self) -> &'static OsStr {
+        match self {
+            Resource::Cpu => OsStr::new("cpu.pressure"),
+            Resource::Memory => OsStr::new("memory.pressure"),
+            Resource::Io => OsStr::new("io.pressure"),
+        }
+    }
+}
+
+impl PressureData {
+    /// Read pressure stall information for the entire host from `/proc/pressure/*`.
+    ///
+    /// ```no_run
+    /// use proxmox_procfs::pressure::*;
+    ///
+    /// let pressure = PressureData::read_system(Resource::Cpu).unwrap();
+    /// println!("{}", pressure.some.average_10);
+    ///
+    ///```
+    pub fn read_system(what: Resource) -> Result<PressureData, Error> {
+        Self::read_file(what.into_proc_path())
+    }
+
+    /// Read pressure stall information for a cgroup.
+    ///
+    /// The `cgroup` parameter will be directly used to assemble the path for the PSI file. For
+    /// instance, if set to `lxc/101`, then `/sys/fs/cgroup/lxc/101/cpu.pressure` will be read.
+    ///
+    /// Note: This functions will return [`Error::NotFound`] in case the pressure file does not exist,
+    /// usually meaning that the cgroup does not exist (any more). This distinct error variant allows
+    /// the caller to differentiate this case from other kinds of IO errors.
+    ///
+    /// ```no_run
+    /// use proxmox_procfs::pressure::{PressureData, Resource};
+    ///
+    /// let pressure = PressureData::read_cgroup("qemu.slice/100.scope", Resource::Cpu).unwrap();
+    /// println!("{}", pressure.some.average_10);
+    ///
+    /// let pressure = PressureData::read_cgroup("lxc/101", Resource::Io).unwrap();
+    /// println!("{}", pressure.some.average_10);
+    ///
+    /// ```
+    pub fn read_cgroup(cgroup: &str, resource: Resource) -> Result<PressureData, Error> {
+        let path = Path::new("/sys/fs/cgroup/")
+            .join(cgroup)
+            .join(resource.into_cgroup_path_component());
+
+        Self::read_file(&path)
+    }
+
+    /// Read pressure stall information from a provided path.
+    ///
+    /// ```no_run
+    /// use proxmox_procfs::pressure::{PressureData, Resource};
+    ///
+    /// let pressure = PressureData::read_file("/proc/pressure/io").unwrap();
+    /// println!("{}", pressure.some.average_10);
+    ///
+    /// ```
+    pub fn read_file<P: AsRef<Path>>(path: P) -> Result<PressureData, Error> {
+        let file = match File::open(path.as_ref()) {
+            Ok(file) => file,
+            Err(err) if err.kind() == ErrorKind::NotFound => {
+                return Err(Error::NotFound(path.as_ref().into()))
+            }
+            Err(err) => return Err(Error::Io(err)),
+        };
+
+        let reader = BufReader::new(file);
+
+        PressureData::read(reader)
+    }
+
+    fn read<R: BufRead>(mut reader: R) -> Result<Self, Error> {
+        // Depending on the length of the 'total' field, one line in the pressure output is around
+        // 60 characters long. Pre-alloc roughly double the size to pretty much eliminate the need
+        // for ever having to resize the vec.
+        let mut buf = Vec::with_capacity(128);
+        let (some_kind, some) = Self::read_pressure_line(&mut reader, &mut buf)?;
+        buf.clear();
+
+        let (full_kind, full) = Self::read_pressure_line(&mut reader, &mut buf)?;
+
+        if some_kind != PressureRecordKind::Some || full_kind != PressureRecordKind::Full {
+            return Err(Error::InvalidFormat(
+                "unexpected pressure record structure".into(),
+            ));
+        }
+
+        Ok(PressureData { some, full })
+    }
+
+    fn read_pressure_line<R: BufRead>(
+        reader: &mut R,
+        buf: &mut Vec<u8>,
+    ) -> Result<(PressureRecordKind, PressureRecord), Error> {
+        // The buffer should be empty. It is only passed by the caller as a performance
+        // optimization
+        debug_assert!(buf.is_empty());
+
+        reader.read_until(b'\n', buf)?;
+        // SAFETY: In production, `reader` is expected to read from
+        // procfs/sysfs pressure files, which only ever should return ASCII strings.
+        let line = unsafe { std::str::from_utf8_unchecked(buf) };
+
+        Self::read_record(line)
+    }
+
+    fn read_record(line: &str) -> Result<(PressureRecordKind, PressureRecord), Error> {
+        let mut iter = line.split_ascii_whitespace();
+
+        let kind = iter
+            .next()
+            .ok_or_else(|| Error::InvalidFormat("missing pressure kind field".into()))
+            .and_then(PressureRecordKind::from_str)?;
+
+        let average_10 = Self::parse_field(iter.next(), "avg10=")?;
+        let average_60 = Self::parse_field(iter.next(), "avg60=")?;
+        let average_300 = Self::parse_field(iter.next(), "avg300=")?;
+        let total = Self::parse_field(iter.next(), "total=")?;
+
+        Ok((
+            kind,
+            PressureRecord {
+                average_10,
+                average_60,
+                average_300,
+                total,
+            },
+        ))
+    }
+
+    fn parse_field<T: FromStr>(s: Option<&str>, prefix: &str) -> Result<T, Error>
+    where
+        <T as FromStr>::Err: std::fmt::Display,
+    {
+        s.and_then(|s| s.strip_prefix(prefix))
+            .ok_or_else(|| {
+                Error::InvalidFormat(format!("expected '{prefix}' prefix for next field"))
+            })?
+            .parse()
+            .map_err(|err| Error::InvalidFormat(format!("failed to parse '{prefix}': {err}")))
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_read_psi() {
+        let s = "some avg10=1.42 avg60=2.09 avg300=1.42 total=40979658
+full avg10=0.08 avg60=0.18 avg300=0.13 total=22865313
+";
+
+        let mut reader = std::io::Cursor::new(s);
+        let stats = PressureData::read(&mut reader).unwrap();
+
+        assert_eq!(stats.some.total, 40979658);
+        assert!((stats.some.average_10 - 1.82).abs() < f64::EPSILON);
+        assert!((stats.some.average_60 - 2.09).abs() < f64::EPSILON);
+        assert!((stats.some.average_300 - 1.42).abs() < f64::EPSILON);
+
+        assert_eq!(stats.full.total, 22865313);
+        assert!((stats.full.average_10 - 0.08).abs() < f64::EPSILON);
+        assert!((stats.full.average_60 - 0.18).abs() < f64::EPSILON);
+        assert!((stats.full.average_300 - 0.13).abs() < f64::EPSILON);
+    }
+
+    #[test]
+    fn test_read_error() {
+        let s = "invalid avg10=1.42 avg60=2.09 avg300=1.42 total=40979658
+full avg10=0.08 avg60=0.18 avg300=0.13 total=22865313
+";
+
+        let mut reader = std::io::Cursor::new(s);
+        assert!(PressureData::read(&mut reader).is_err());
+    }
+
+    #[test]
+    fn test_invalid_field() {
+        let s = "some foo=1.42 avg60=2.09 avg300=1.42 total=40979658
+full avg10=0.08 avg60=0.18 avg300=0.13 total=22865313
+";
+
+        let mut reader = std::io::Cursor::new(s);
+        assert!(PressureData::read(&mut reader).is_err());
+    }
+
+    #[test]
+    fn test_read_system_pressure() {
+        for resource in [Resource::Io, Resource::Memory, Resource::Cpu] {
+            PressureData::read_system(resource).unwrap();
+        }
+    }
+
+    #[test]
+    fn test_read_cgroup_pressure() {
+        for resource in [Resource::Io, Resource::Memory, Resource::Cpu] {
+            PressureData::read_cgroup("system.slice", resource).unwrap();
+        }
+    }
+
+    #[test]
+    fn test_read_file_notfound() {
+        assert!(matches!(
+            PressureData::read_file("/invalid"),
+            Err(Error::NotFound(_))
+        ))
+    }
+}
-- 
2.47.3





  parent reply	other threads:[~2026-03-12 13:53 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-12 13:52 [PATCH datacenter-manager/proxmox{,-backup,-yew-comp} 00/26] metric collection for the PDM host Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 01/26] sys: procfs: don't read from sysfs during unit tests Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 02/26] parallel-handler: import code from Proxmox Backup Server Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 03/26] parallel-handler: introduce custom error type Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 04/26] parallel-handler: add documentation Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 05/26] parallel-handler: add simple unit-test suite Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 06/26] disks: import from Proxmox Backup Server Lukas Wagner
2026-03-16 13:13   ` Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox 07/26] disks: fix typo in `initialize_gpt_disk` Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 08/26] disks: add parts of gather_disk_stats from PBS Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 09/26] disks: gate api macro behind 'api-types' feature Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox 10/26] disks: clippy: collapse if-let chains where possible Lukas Wagner
2026-03-12 13:52 ` Lukas Wagner [this message]
2026-03-16 13:25   ` [PATCH proxmox 11/26] procfs: add helpers for querying pressure stall information Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox 12/26] time: use u64 parse helper from nom Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-backup 13/26] tools: move ParallelHandler to new proxmox-parallel-handler crate Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-backup 14/26] tools: replace disks module with proxmox-disks Lukas Wagner
2026-03-16 13:27   ` Arthur Bied-Charreton
2026-03-12 13:52 ` [PATCH proxmox-backup 15/26] metric collection: use blockdev_stat_for_path from proxmox_disks Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 16/26] node status panel: add `children` property Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 17/26] RRDGrid: fix size observer by attaching node reference to rendered container Lukas Wagner
2026-03-12 13:52 ` [PATCH proxmox-yew-comp 18/26] RRDGrid: add padding and increase gap between elements Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 19/26] metric collection: clarify naming for remote metric collection Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 20/26] metric collection: fix minor typo in error message Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 21/26] metric collection: collect PDM host metrics in a new collection task Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 22/26] api: fix /nodes/localhost/rrddata endpoint Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 23/26] pdm: node rrd data: rename 'total-time' to 'metric-collection-total-time' Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 24/26] pdm-api-types: add PDM host metric fields Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 25/26] ui: node status: add RRD graphs for PDM host metrics Lukas Wagner
2026-03-12 13:52 ` [PATCH datacenter-manager 26/26] ui: lxc/qemu/node: use RRD value render helpers Lukas Wagner
2026-03-16 13:42 ` [PATCH datacenter-manager/proxmox{,-backup,-yew-comp} 00/26] metric collection for the PDM host Arthur Bied-Charreton

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=20260312135229.420729-12-l.wagner@proxmox.com \
    --to=l.wagner@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