package PVE::PVEMod::Collector::Ups; use strict; use warnings; use Exporter 'import'; use JSON; use PVE::PVEMod::Config qw(%config $process_type $ups_state_file); use PVE::PVEMod::Utils qw(debug setup_collector_signals); our @EXPORT_OK = qw( collector_for_ups ); # ============================================================================ # UPS — long-running collector # ============================================================================ sub collector_for_ups { my ($device) = @_; $process_type = 'collector'; $0 = "collector-ups-$device->{ups_name}"; debug(__LINE__, "UPS collector started"); my $shutdown = 0; setup_collector_signals("ups-$device->{ups_name}", \$shutdown); while (!$shutdown) { my $ups_data = _get_ups_status($device->{ups_name}); eval { open my $ofh, '>', $ups_state_file or die "Failed to open $ups_state_file: $!"; print $ofh $ups_data; close $ofh; debug(__LINE__, "Wrote UPS data to $ups_state_file"); }; if ($@) { debug(__LINE__, "Error writing UPS data: $@"); } sleep $config{intervals}{data_pull} unless $shutdown; } debug(__LINE__, "UPS collector shutting down"); exit 0; } # ============================================================================ # UPS — status query # ============================================================================ sub _get_ups_status { my ($ups_name) = @_; debug(__LINE__, "Collecting UPS status for $ups_name"); my $output; if ($config{debug}{ups_mode} && -f $config{debug}{ups_output_file}) { debug(__LINE__, "Debug mode: reading UPS data from $config{debug}{ups_output_file}"); open my $fh, '<', $config{debug}{ups_output_file} or do { debug(__LINE__, "Failed to open debug file $config{debug}{ups_output_file}: $!"); return encode_json({ error => "Failed to open debug file" }); }; local $/; $output = <$fh>; close $fh; } else { $output = `/usr/bin/upsc $ups_name 2>/dev/null`; } unless (defined $output && length($output) > 0) { debug(__LINE__, "No output from upsc for $ups_name"); return encode_json({ error => "No data from UPS $ups_name" }); } my $ups_data = _parse_upsc_output($output); unless (keys %$ups_data) { debug(__LINE__, "No data received from upsc for $ups_name"); return encode_json({ error => "No data from UPS $ups_name" }); } return JSON->new->pretty->canonical->encode({ $ups_name => $ups_data }); } # ============================================================================ # UPS — output parser # ============================================================================ sub _parse_upsc_output { my ($output) = @_; my $ups_data = {}; debug(__LINE__, "Parsing upsc output"); eval { foreach my $line (split /\n/, $output) { next if $line =~ /^\s*$/; next if $line =~ /^Init SSL/; if ($line =~ /^([^:]+):\s*(.*)$/) { my ($key, $value) = ($1, $2); $key =~ s/^\s+|\s+$//g; $value =~ s/^\s+|\s+$//g; # Coerce numeric values if ($value =~ /^-?\d+\.?\d*$/) { $ups_data->{$key} = $value + 0; } else { $ups_data->{$key} = $value; } } } }; if ($@) { debug(__LINE__, "Error parsing upsc output: $@"); } debug(__LINE__, "Completed parsing upsc output"); return $ups_data; } 1;