public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH container 1/1] api: add hardware sensors endpoint and API support
       [not found] <20251014193609.34452-1-davide.guerri@gmail.com>
@ 2025-10-14 19:36 ` Davide Guerri via pve-devel
  0 siblings, 0 replies; only message in thread
From: Davide Guerri via pve-devel @ 2025-10-14 19:36 UTC (permalink / raw)
  To: pve-devel; +Cc: Davide Guerri

[-- Attachment #1: Type: message/rfc822, Size: 20516 bytes --]

From: Davide Guerri <davide.guerri@gmail.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH container 1/1] api: add hardware sensors endpoint and API support
Date: Tue, 14 Oct 2025 21:36:09 +0200
Message-ID: <20251014193609.34452-2-davide.guerri@gmail.com>

This change adds support for querying hardware sensors via the API,
enhancing system monitoring capabilities.

API hierarchy:
/sensors       -> GET index (returns: temperature, fan)
  /temperature -> GET index (returns: cpu, disk, other)
    /cpu       -> GET CPU temperature data
    /disk      -> GET disk temperature data
    /other     -> GET other temperature data
  /fan         -> GET fan speed data

Signed-off-by: Davide Guerri <davide.guerri@gmail.com>
---
 PVE/API2/Hardware.pm         |   8 +-
 PVE/API2/Hardware/Makefile   |   1 +
 PVE/API2/Hardware/Sensors.pm | 420 +++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+), 1 deletion(-)
 create mode 100644 PVE/API2/Hardware/Sensors.pm

diff --git a/PVE/API2/Hardware.pm b/PVE/API2/Hardware.pm
index 48b0765a..31391d92 100644
--- a/PVE/API2/Hardware.pm
+++ b/PVE/API2/Hardware.pm
@@ -7,6 +7,7 @@ use PVE::JSONSchema qw(get_standard_option);
 use PVE::RESTHandler;
 
 use PVE::API2::Hardware::PCI;
+use PVE::API2::Hardware::Sensors;
 use PVE::API2::Hardware::USB;
 
 use base qw(PVE::RESTHandler);
@@ -16,6 +17,11 @@ __PACKAGE__->register_method({
     path => 'pci',
 });
 
+__PACKAGE__->register_method({
+    subclass => "PVE::API2::Hardware::Sensors",
+    path => 'sensors',
+});
+
 __PACKAGE__->register_method({
     subclass => "PVE::API2::Hardware::USB",
     path => 'usb',
@@ -47,7 +53,7 @@ __PACKAGE__->register_method({
         my ($param) = @_;
 
         my $res = [
-            { type => 'pci' }, { type => 'usb' },
+            { type => 'pci' }, { type => 'sensors' }, { type => 'usb' },
         ];
 
         return $res;
diff --git a/PVE/API2/Hardware/Makefile b/PVE/API2/Hardware/Makefile
index 026a8dd6..36f359cc 100644
--- a/PVE/API2/Hardware/Makefile
+++ b/PVE/API2/Hardware/Makefile
@@ -2,6 +2,7 @@ include ../../../defines.mk
 
 PERLSOURCE=			\
 	PCI.pm			\
+	Sensors.pm	\
 	USB.pm			\
 
 all:
diff --git a/PVE/API2/Hardware/Sensors.pm b/PVE/API2/Hardware/Sensors.pm
new file mode 100644
index 00000000..4509b88b
--- /dev/null
+++ b/PVE/API2/Hardware/Sensors.pm
@@ -0,0 +1,420 @@
+package PVE::API2::Hardware::Sensors;
+
+use v5.36;
+
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RESTHandler;
+use PVE::SensorInfo;
+
+use base qw(PVE::RESTHandler);
+
+# Helper function to add optional fields to an entry
+sub add_optional_fields {
+    my ($entry, $info, $fields) = @_;
+
+    for my $field (@$fields) {
+        if (exists $info->{$field}) {
+            $entry->{$field} = $info->{$field};
+        }
+    }
+
+    return $entry;
+}
+
+__PACKAGE__->register_method({
+    name => 'sensor_index',
+    path => '',
+    method => 'GET',
+    description => "Index of available sensor methods.",
+    permissions => {
+        user => 'all',
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => { subdir => { type => 'string' } },
+        },
+        links => [{ rel => 'child', href => "{subdir}" }],
+    },
+    code => sub {
+        my ($param) = @_;
+
+        return [
+            { subdir => 'temperature' }, { subdir => 'fan' },
+        ];
+    },
+});
+
+__PACKAGE__->register_method({
+    name => 'temperature_index',
+    path => 'temperature',
+    method => 'GET',
+    description => "Index of temperature sensor types.",
+    permissions => {
+        user => 'all',
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => { subdir => { type => 'string' } },
+        },
+        links => [{ rel => 'child', href => "{subdir}" }],
+    },
+    code => sub {
+        my ($param) = @_;
+
+        return [
+            { subdir => 'cpu' }, { subdir => 'disk' }, { subdir => 'other' },
+        ];
+    },
+});
+
+__PACKAGE__->register_method({
+    name => 'read_cpu_temperature_values',
+    path => 'temperature/cpu',
+    method => 'GET',
+    description => "Read current CPU temperature values.",
+    protected => 1,
+    proxyto => "node",
+    permissions => {
+        check => ['perm', '/', ['Sys.Audit'], any => 1],
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => {
+                sensor => {
+                    type => 'string',
+                    description => 'Sensor identifier (e.g., coretemp/Core 0).',
+                },
+                temperature => {
+                    type => 'number',
+                    description => 'Current temperature in degrees Celsius.',
+                },
+                unit => {
+                    type => 'string',
+                    description => 'Temperature unit (always celsius).',
+                },
+                driver => {
+                    type => 'string',
+                    description => 'Human-readable driver description.',
+                },
+                max => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Maximum operating temperature before throttling.',
+                },
+                critical => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Critical temperature threshold.',
+                },
+                logical_core => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'Logical core number.',
+                },
+                physical_core => {
+                    type => 'string',
+                    optional => 1,
+                    description => 'Physical core ID.',
+                },
+                package => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'CPU package/socket number.',
+                },
+            },
+        },
+    },
+    code => sub {
+        my ($param) = @_;
+
+        my $temps = PVE::SensorInfo::read_temperatures('cpu');
+
+        my $result = [];
+        foreach my $sensor (sort keys %$temps) {
+            my $info = $temps->{$sensor};
+
+            my $entry = {
+                sensor => $sensor,
+                temperature => $info->{temperature},
+                unit => $info->{unit},
+                driver => $info->{driver},
+            };
+
+            add_optional_fields(
+                $entry,
+                $info,
+                [qw(max critical logical_core physical_core package)],
+            );
+
+            push @$result, $entry;
+        }
+
+        return $result;
+    },
+});
+
+__PACKAGE__->register_method({
+    name => 'read_disk_temperature_values',
+    path => 'temperature/disk',
+    method => 'GET',
+    description => "Read current disk temperature values.",
+    protected => 1,
+    proxyto => "node",
+    permissions => {
+        check => ['perm', '/', ['Sys.Audit'], any => 1],
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => {
+                sensor => {
+                    type => 'string',
+                    description => 'Sensor identifier (e.g., hwmon1/Composite).',
+                },
+                device => {
+                    type => 'string',
+                    optional => 1,
+                    description => 'Block device name (e.g., nvme0n1, sda).',
+                },
+                temperature => {
+                    type => 'number',
+                    description => 'Current temperature in degrees Celsius.',
+                },
+                unit => {
+                    type => 'string',
+                    description => 'Temperature unit (always celsius).',
+                },
+                driver => {
+                    type => 'string',
+                    description => 'Human-readable driver description.',
+                },
+                max => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Maximum operating temperature.',
+                },
+                critical => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Critical temperature threshold.',
+                },
+            },
+        },
+    },
+    code => sub {
+        my ($param) = @_;
+
+        my $temps = PVE::SensorInfo::read_temperatures('disk');
+
+        my $result = [];
+        foreach my $sensor (sort keys %$temps) {
+            my $info = $temps->{$sensor};
+
+            my $entry = {
+                sensor => $sensor,
+                temperature => $info->{temperature},
+                unit => $info->{unit},
+                driver => $info->{driver},
+            };
+
+            add_optional_fields($entry, $info, [qw(device max critical)]);
+
+            push @$result, $entry;
+        }
+
+        return $result;
+    },
+});
+
+__PACKAGE__->register_method({
+    name => 'read_other_temperature_values',
+    path => 'temperature/other',
+    method => 'GET',
+    description => "Read current temperature values from other sensors.",
+    protected => 1,
+    proxyto => "node",
+    permissions => {
+        check => ['perm', '/', ['Sys.Audit'], any => 1],
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => {
+                sensor => {
+                    type => 'string',
+                    description => 'Sensor identifier.',
+                },
+                temperature => {
+                    type => 'number',
+                    description => 'Current temperature in degrees Celsius.',
+                },
+                unit => {
+                    type => 'string',
+                    description => 'Temperature unit (always celsius).',
+                },
+                driver => {
+                    type => 'string',
+                    description => 'Human-readable driver description.',
+                },
+                max => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Maximum operating temperature.',
+                },
+                critical => {
+                    type => 'number',
+                    optional => 1,
+                    description => 'Critical temperature threshold.',
+                },
+            },
+        },
+    },
+    code => sub {
+        my ($param) = @_;
+
+        my $temps = PVE::SensorInfo::read_temperatures('other');
+
+        my $result = [];
+        foreach my $sensor (sort keys %$temps) {
+            my $info = $temps->{$sensor};
+
+            my $entry = {
+                sensor => $sensor,
+                temperature => $info->{temperature},
+                unit => $info->{unit},
+                driver => $info->{driver},
+            };
+
+            add_optional_fields($entry, $info, [qw(max critical)]);
+
+            push @$result, $entry;
+        }
+
+        return $result;
+    },
+});
+
+__PACKAGE__->register_method({
+    name => 'read_fan_speeds',
+    path => 'fan',
+    method => 'GET',
+    description => "Read current fan speed values.",
+    protected => 1,
+    proxyto => "node",
+    permissions => {
+        check => ['perm', '/', ['Sys.Audit'], any => 1],
+    },
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+        },
+    },
+    returns => {
+        type => 'array',
+        items => {
+            type => "object",
+            properties => {
+                fan => {
+                    type => 'string',
+                    description => 'Fan identifier (e.g., nct6775/CPU Fan).',
+                },
+                speed => {
+                    type => 'integer',
+                    description => 'Current fan speed in RPM.',
+                },
+                unit => {
+                    type => 'string',
+                    description => 'Speed unit (always rpm).',
+                },
+                driver => {
+                    type => 'string',
+                    description => 'Human-readable driver description.',
+                },
+                min => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'Minimum fan speed in RPM.',
+                },
+                max => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'Maximum fan speed in RPM.',
+                },
+                target => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'Target fan speed in RPM.',
+                },
+                alarm => {
+                    type => 'integer',
+                    optional => 1,
+                    description => 'Fan alarm status (0=OK, 1=Alarm).',
+                },
+            },
+        },
+    },
+    code => sub {
+        my ($param) = @_;
+
+        my $speeds = PVE::SensorInfo::read_fan_speeds();
+
+        my $result = [];
+        foreach my $fan (sort keys %$speeds) {
+            my $info = $speeds->{$fan};
+
+            my $entry = {
+                fan => $fan,
+                speed => $info->{speed},
+                unit => $info->{unit},
+                driver => $info->{driver},
+            };
+
+            add_optional_fields($entry, $info, [qw(min max target alarm)]);
+
+            push @$result, $entry;
+        }
+
+        return $result;
+    },
+});
+
+1;
-- 
2.50.1 (Apple Git-155)



[-- Attachment #2: Type: text/plain, Size: 160 bytes --]

_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-10-14 21:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20251014193609.34452-1-davide.guerri@gmail.com>
2025-10-14 19:36 ` [pve-devel] [PATCH container 1/1] api: add hardware sensors endpoint and API support Davide Guerri via pve-devel

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