#!/usr/bin/env perl # Copyright (c) 2025, R. W. Rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # all_ipmi # IPMI sensor that emits old network.ipmi and system.Power* keys. # Replaces all_ipmi_network and all_ipmi_power. # # Revision History: # 2026-10-12 Copilot v1.0.0 r? initial v3.8.0 unix IPMI sensor # 2026-10-12 Claude v1.0.0 v3.7.1 lookup, Power Draw and key rules restored (F2a) # 2026-10-18 Claude renamed from unix_ipmi (bug 210) package all_ipmi; use strict; use warnings; use version; our $VERSION = version->declare('v1.0.0'); use FindBin; use File::Spec; use lib File::Spec->catdir($FindBin::Bin), File::Spec->catdir("$FindBin::Bin/.."); use library qw(:all); ####################################################### # # parseIpmiLan( $lanText ) # # Parses ipmitool lan print output into old network.ipmi keys. # # Parameters: # $lanText - text from ipmitool lan print # # Returns: # array reference of output lines # # Side Effects: # none # ####################################################### sub parseIpmiLan { my $lanText = shift; my @out; # same lookups as v3.7.1 all_ipmi_network: first line holding each key wins my %storage = ( 'address' => { 'key' => 'IP Address' }, 'netmask' => { 'key' => 'Subnet Mask' }, 'mac' => { 'key' => 'MAC Address' }, ); foreach my $line (split(/\n/, defined($lanText) ? $lanText : '')) { my ($key, $value) = split(/\s+:\s+/, $line); next unless defined($key) && defined($value); foreach my $test (keys %storage) { next if defined($storage{$test}{'value'}); if ($key eq $storage{$test}{'key'}) { $storage{$test}{'value'} = $value; last; } } } foreach my $key ('address', 'netmask', 'mac') { push @out, "network\tipmi\t$key\t$storage{$key}{'value'}" if defined($storage{$key}{'value'}); } return \@out; } ####################################################### # # parseIpmiPower( $sensorText ) # # Parses ipmitool sensor output into old system.Power* keys, with # the same rules as v3.7.1 all_ipmi_power: Current and Voltage readings # are listed and multiplied pairwise, Power Supply readings in Watts are # listed and added, and a Power Meter reading ends the scan and stands # alone as Power Draw. # # Parameters: # $sensorText - text from ipmitool sensor (pipe separated) # # Returns: # array reference of output lines # # Side Effects: # none # ####################################################### sub parseIpmiPower { my $sensorText = shift; my @out; my (@current, @voltage, @power); foreach my $line (split(/\n/, defined($sensorText) ? $sensorText : '')) { last unless $line; my @fields = split(/\s*\|\s*/, $line); next unless @fields >= 3; next if $fields[1] eq 'na'; if ($fields[0] =~ /Current.*\d*/) { push @current, $fields[1]; push @out, "system\tPower $fields[0]\t$fields[1] $fields[2]"; } elsif ($fields[0] =~ /Voltage.*\d*/) { push @voltage, $fields[1]; push @out, "system\tPower $fields[0]\t$fields[1] $fields[2]"; } elsif ($fields[0] =~ /Power Supply.*\d*/ && $fields[2] eq 'Watts') { push @power, $fields[1]; push @out, "system\t$fields[0] Draw\t$fields[1] $fields[2]"; } elsif ($fields[0] eq 'Power Meter') { push @out, "system\tPower Draw\t$fields[1] $fields[2]"; return \@out; # the meter is authoritative; no summing } } my $power = 0; no warnings 'numeric'; # as v3.7.1: a non-numeric reading counts as 0 for (my $i = 0; $i < @current && $i < @voltage; $i++) { $power += $current[$i] * $voltage[$i]; } $power += $_ foreach @power; push @out, "system\tPower Draw\t$power Watts" if $power; return \@out; } ####################################################### # # main() # # Runs IPMI probes when local device files are present. # # Parameters: # none # # Returns: # integer exit status # # Side Effects: # runs ipmitool and writes STDOUT/STDERR # ####################################################### sub main { if (!checkOS({ 'linux' => undef, 'freebsd' => undef })) { print STDERR "unsupported os\n"; return 1; } unless (validCommandOnSystem({ 'ipmitool' => undef })) { print STDERR "ipmitool not found\n"; return 2; } if (!-e '/dev/ipmi0' && !-e '/dev/ipmi/0' && !-e '/dev/ipmidev/0') { print STDERR "no IPMI device\n"; return 2; } my $lan = runCommand('ipmitool', 'lan', 'print'); my $sensors = runCommand('ipmitool', 'sensor'); unless (defined($lan) && $lan ne '') { # ipmitool installed, but no driver; probably used to reach elsewhere print STDERR "ipmitool lan print returned nothing\n"; return 2; } my @out; push @out, @{parseIpmiLan($lan)}; push @out, @{parseIpmiPower($sensors)}; print join("\n", @out), "\n" if @out; return @out ? 0 : 1; } exit main() unless caller; 1;