From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 8291C1FF17A for ; Tue, 14 Oct 2025 23:21:23 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 740EFCB13; Tue, 14 Oct 2025 23:21:39 +0200 (CEST) To: pve-devel@lists.proxmox.com Date: Tue, 14 Oct 2025 21:36:09 +0200 In-Reply-To: <20251014193609.34452-1-davide.guerri@gmail.com> References: <20251014193609.34452-1-davide.guerri@gmail.com> MIME-Version: 1.0 Message-ID: List-Id: Proxmox VE development discussion List-Post: From: Davide Guerri via pve-devel Precedence: list Cc: Davide Guerri X-Mailman-Version: 2.1.29 X-BeenThere: pve-devel@lists.proxmox.com List-Subscribe: , List-Unsubscribe: , List-Archive: Reply-To: Proxmox VE development discussion List-Help: Subject: [pve-devel] [PATCH container 1/1] api: add hardware sensors endpoint and API support Content-Type: multipart/mixed; boundary="===============7756709703528944938==" Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" --===============7756709703528944938== Content-Type: message/rfc822 Content-Disposition: inline Return-Path: X-Original-To: pve-devel@lists.proxmox.com Delivered-To: pve-devel@lists.proxmox.com Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id D595DDD78A for ; Tue, 14 Oct 2025 23:21:38 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BB24FCA96 for ; Tue, 14 Oct 2025 23:21:38 +0200 (CEST) Received: from mail-wr1-x431.google.com (mail-wr1-x431.google.com [IPv6:2a00:1450:4864:20::431]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS for ; Tue, 14 Oct 2025 23:21:37 +0200 (CEST) Received: by mail-wr1-x431.google.com with SMTP id ffacd0b85a97d-4060b4b1200so4474468f8f.3 for ; Tue, 14 Oct 2025 14:21:37 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1760476891; x=1761081691; darn=lists.proxmox.com; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=KzAFDNx7djWqM+3IaWfTnTQ3h0Ouw63fGb72iDBDtiU=; b=IRa7HvZoNU2362/F7sMpKMyb82W9fh1EhH3z4Pge6K4VS7CkeX7ejTeKIo61Q8F4UX AfwNnbRYgTLwFH3zPBW1H4J+6WtyzC2vIaGf2vt75xqdZS78JUFiGWRS7DdQAaMMK0r8 7QJmPv8vETsdbQChr+rSKSjCpBh8afiksmPKQczrqzqIPkL6HASJS/qGCN2a0omSp9O4 QCnh/xwNKQ2YA4nIGe+mxrF8ubOYyNovGrmGfqGgTqL7OtKqxrWj+kIzOWwVApo37Tmz ih/RW72axVPKkev2SIPCcCuC3CBWbx7lNrhwgkN002At9pK4WEWyl9IEUGBsIt0kB/RS q3+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1760476891; x=1761081691; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=KzAFDNx7djWqM+3IaWfTnTQ3h0Ouw63fGb72iDBDtiU=; b=Awq0vmgB6GnBKNqFSXa8KTfpCF5ylZx8EON0QKFnDN9fZoPft7M2gbDT6wqW0Pj4rY S++SRCSPfCfcpwnAu453aVQvY7N5zzDkWHB8vwQ8ZyOA61Py380j6BedOwvoeQ6MVmeL nBtN5Folg3OA9mf+mfsdv6HQFN7EQc+5UWWo+/+odfjLUDE9FtZuLvaM2Is5bfSxIOEz vrZ3Fyn0c6yCx/5xGbRJARcwMhiDxNQF9LJ0AvrakhxLr4Y/aCWoOV4Ri45X+hG04B4o P4lqm9Tha19Ss6jW01zA2BIVFJETr16H564pn6YbEqAabHg1GYhXzZR1flKiN/LEjoZm DqrQ== X-Gm-Message-State: AOJu0Yx6FowDSRxxW2oN5+EBOl2EMgOibulKYImxy4FEQNupY2hJ4RB9 GnqOzhsd6T0iKQWSq0BNpG1hzDBvwvnxY+fO/4RTU5YOqu10WF1/jWAw X-Gm-Gg: ASbGnct13QncVpyAdwsJttwUAueKBlcXqC3fwTD1dCKvB+hPYgX6rKdpTFzKVL+P569 uNzips0NdfH2OB9IKDUQKsDAfZLJO8g81jKTKQtX8tuxrUrZaaza1IEZfpg2+VhZgTKQXeraKkA 6lzQO/2haIl05cBMAvJFzodp3vnZuGz3a53nZ2uyWWfe3lrnjmCnimR9s8TwW+/jKHIlNDQBQ3F LvK8h4wbXQErDdI0GPR5nFp69ntmDRSAXidp483lnNboFKh2XfaKHCMvD/yu10BdV4UJFl4/Rnb 7tDWNdqgb69+a1Tm1aISnqwGn4tE8qYZYoUNGqrBbtNysgNG2HQ3GR734kZodFoiEHuQxWP0O4K 0VHB2v81BmkzZY1iJk4yoWgQbkeeQ2e+lp1X/zE7YAxuvWEa8C5B0e6Bh7i+6JfPJi7Uto7Z6f7 C1BL8hyn7nyYAu5lWppd/xNmRazJDEj1r6KL6i8hHVlG3RaVOSwjho6A== X-Google-Smtp-Source: AGHT+IG6+SMBd2f8tdVAnNcWYCa/384Rt6K/wfrgVci7eXrYPJC8+pUDe4n5+lz4J1cf9TmvScO0lg== X-Received: by 2002:a05:6000:2903:b0:3e8:d0f6:93b9 with SMTP id ffacd0b85a97d-4266e8f7f40mr14690183f8f.34.1760476890431; Tue, 14 Oct 2025 14:21:30 -0700 (PDT) Received: from lechuck (host-79-43-235-239.retail.telecomitalia.it. [79.43.235.239]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-426ce583335sm24931761f8f.18.2025.10.14.14.21.28 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Tue, 14 Oct 2025 14:21:29 -0700 (PDT) Received: by lechuck.localdomain (Postfix, from userid 502) id ADD607527B2A; Tue, 14 Oct 2025 21:36:11 +0200 (CEST) From: Davide Guerri 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> X-Mailer: git-send-email 2.50.1 In-Reply-To: <20251014193609.34452-1-davide.guerri@gmail.com> References: <20251014193609.34452-1-davide.guerri@gmail.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.075 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy FREEMAIL_FROM 0.001 Sender email is commonly abused enduser mail provider POISEN_SPAM_PILL 0.1 Meta: its spam POISEN_SPAM_PILL_1 0.1 random spam to be learned in bayes POISEN_SPAM_PILL_3 0.1 random spam to be learned in bayes RCVD_IN_DNSWL_NONE -0.0001 Sender listed at https://www.dnswl.org/, no trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [usb.pm,defines.mk,hardware.pm,sensors.pm,pci.pm] 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 --- 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) --===============7756709703528944938== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel --===============7756709703528944938==--