[PATCH ha-manager v3 20/40] env: pve2: implement dynamic node and service stats

Daniel Kral
2026-03-30T14:41:44Z

Fetch the dynamic node and service stats with rrd_dump(), which is
periodically sampled and broadcasted by the PVE nodes' pvestatd service
and propagated through the pmxcfs.

Signed-off-by: Daniel Kral <d.kral@proxmox.com>
Reviewed-by: Dominik Rusovac <d.rusovac@proxmox.com>
---
changes v2 -> v3:
- remove unused $id parameter from get_dynamic_service_stats() as
suggested by @Thomas and @Dominik

src/PVE/HA/Env/PVE2.pm | 63 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)

diff --git a/src/PVE/HA/Env/PVE2.pm b/src/PVE/HA/Env/PVE2.pm
index 04cd1bfe..b2488ddd 100644
--- a/src/PVE/HA/Env/PVE2.pm
+++ b/src/PVE/HA/Env/PVE2.pm
@@ -42,6 +42,19 @@ my $lockdir = "/etc/pve/priv/lock";
# taken from PVE::Service::pvestatd::update_{lxc,qemu}_status()
use constant {
RRD_VM_INDEX_STATUS => 2,
+ RRD_VM_INDEX_MAXCPU => 5,
+ RRD_VM_INDEX_CPU => 6,
+ RRD_VM_INDEX_MAXMEM => 7,
+ RRD_VM_INDEX_MEM => 8,
+};
+
+# rrd entry indices for PVE nodes
+# taken from PVE::Service::pvestatd::update_node_status()
+use constant {
+ RRD_NODE_INDEX_MAXCPU => 4,
+ RRD_NODE_INDEX_CPU => 5,
+ RRD_NODE_INDEX_MAXMEM => 7,
+ RRD_NODE_INDEX_MEM => 8,
};

sub new {
@@ -569,6 +582,30 @@ sub get_static_service_stats {
return $stats;
}

+sub get_dynamic_service_stats {
+ my ($self) = @_;
+
+ my $rrd = PVE::Cluster::rrd_dump();
+
+ my $stats = get_cluster_service_stats();
+ for my $sid (keys %$stats) {
+ my $id = $stats->{$sid}->{id};
+ my $rrdentry = $rrd->{"pve-vm-9.0/$id"} // [];
+
+ # NOTE the guests' broadcasted vmstatus() caps maxcpu at the node's maxcpu
+ my $maxcpu = ($rrdentry->[RRD_VM_INDEX_MAXCPU] || 0.0) + 0.0;
+
+ $stats->{$sid}->{usage} = {
+ maxcpu => $maxcpu,
+ cpu => (($rrdentry->[RRD_VM_INDEX_CPU] || 0.0) + 0.0) * $maxcpu,
+ maxmem => int($rrdentry->[RRD_VM_INDEX_MAXMEM] || 0),
+ mem => int($rrdentry->[RRD_VM_INDEX_MEM] || 0),
+ };
+ }
+
+ return $stats;
+}
+
sub get_static_node_stats {
my ($self) = @_;

@@ -588,6 +625,32 @@ sub get_static_node_stats {
return $stats;
}

+sub get_dynamic_node_stats {
+ my ($self) = @_;
+
+ my $rrd = PVE::Cluster::rrd_dump();
+
+ my $stats = {};
+ for my $key (keys %$rrd) {
+ my ($nodename) = $key =~ m/^pve-node-9.0\/(\w+)$/;
+
+ next if !$nodename;
+
+ my $rrdentry = $rrd->{$key} // [];
+
+ my $maxcpu = int($rrdentry->[RRD_NODE_INDEX_MAXCPU] || 0);
+
+ $stats->{$nodename} = {
+ maxcpu => $maxcpu,
+ cpu => (($rrdentry->[RRD_NODE_INDEX_CPU] || 0.0) + 0.0) * $maxcpu,
+ maxmem => int($rrdentry->[RRD_NODE_INDEX_MAXMEM] || 0),
+ mem => int($rrdentry->[RRD_NODE_INDEX_MEM] || 0),
+ };
+ }
+
+ return $stats;
+}
+
sub get_node_version {
my ($self, $node) = @_;

--
2.47.3