all lists on 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal