Add UPS support. Refactor file namings
This commit is contained in:
parent
0d608abf6f
commit
db653e4c4b
265
GPUcollecter.pm
265
GPUcollecter.pm
@ -42,9 +42,11 @@ my $pve_mod_working_dir = '/run/pveproxy/pve-mod';
|
|||||||
my $stats_dir = $pve_mod_working_dir;
|
my $stats_dir = $pve_mod_working_dir;
|
||||||
my $state_file = "$pve_mod_working_dir/stats.json";
|
my $state_file = "$pve_mod_working_dir/stats.json";
|
||||||
my $sensors_state_file = "$pve_mod_working_dir/sensors.json";
|
my $sensors_state_file = "$pve_mod_working_dir/sensors.json";
|
||||||
my $lock_file = "$pve_mod_working_dir/pve-mod-worker.lock";
|
my $ups_state_file = "$pve_mod_working_dir/ups.json";
|
||||||
my $pve_mod_worker_lock = "$pve_mod_working_dir/pve-mod-pve_mod_worker.lock";
|
|
||||||
my $startup_lock = $lock_file . ".startup";
|
my $collectors_pid_file = "$pve_mod_working_dir/collectors.pids"; # List of collector PIDs
|
||||||
|
my $pve_mod_worker_lock = "$pve_mod_working_dir/pve-mod-pve_mod_worker.lock"; # PVE Mod worker lock
|
||||||
|
my $startup_lock = "$pve_mod_working_dir/startup.lock"; # Exclusive startup lock
|
||||||
my $last_snapshot = {};
|
my $last_snapshot = {};
|
||||||
my $last_mtime = 0;
|
my $last_mtime = 0;
|
||||||
my $is_collector_parent = 0; # Flag to track if this process started collectors
|
my $is_collector_parent = 0; # Flag to track if this process started collectors
|
||||||
@ -54,9 +56,15 @@ my $COLLECTOR_TIMEOUT = 10; # Stop collectors x seconds after last get_graphic
|
|||||||
my $intel_gpu_enabled = 1; # Set to 0 to disable Intel GPU support
|
my $intel_gpu_enabled = 1; # Set to 0 to disable Intel GPU support
|
||||||
my $amd_gpu_enabled = 0; # Set to 1 to enable AMD GPU support (not yet implemented)
|
my $amd_gpu_enabled = 0; # Set to 1 to enable AMD GPU support (not yet implemented)
|
||||||
my $nvidia_gpu_enabled = 1; # Set to 1 to enable NVIDIA GPU support (not yet implemented)
|
my $nvidia_gpu_enabled = 1; # Set to 1 to enable NVIDIA GPU support (not yet implemented)
|
||||||
|
my $ups_enabled = 1; # Set to 1 to enable UPS support
|
||||||
my $pve_mod_worker_pid;
|
my $pve_mod_worker_pid;
|
||||||
my $pve_mod_worker_running = 0;
|
my $pve_mod_worker_running = 0;
|
||||||
|
|
||||||
|
# UPS Configuration
|
||||||
|
my $ups_device = {
|
||||||
|
ups_name => 'ups@192.168.3.2', # Format: upsname[@hostname[:port]]
|
||||||
|
};
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Intel GPU Support
|
# Intel GPU Support
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -985,11 +993,136 @@ sub _cpu_model_by_package {
|
|||||||
return "unknown";
|
return "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# UPS Support
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
sub _collector_for_ups {
|
||||||
|
my ($device) = @_;
|
||||||
|
|
||||||
|
$0 = "pve-mod-ups-collector";
|
||||||
|
_debug(__LINE__, "UPS collector started");
|
||||||
|
|
||||||
|
# Set up signal handlers for graceful shutdown
|
||||||
|
my $shutdown = 0;
|
||||||
|
$SIG{TERM} = sub {
|
||||||
|
_debug(__LINE__, "UPS collector received SIGTERM");
|
||||||
|
$shutdown = 1;
|
||||||
|
};
|
||||||
|
$SIG{INT} = sub {
|
||||||
|
_debug(__LINE__, "UPS collector received SIGINT");
|
||||||
|
$shutdown = 1;
|
||||||
|
};
|
||||||
|
while (!$shutdown) {
|
||||||
|
my $upsData = _get_ups_status($device->{ups_name});
|
||||||
|
|
||||||
|
# Write to ups state file
|
||||||
|
eval {
|
||||||
|
open my $ofh, '>', $ups_state_file or die "Failed to open $ups_state_file: $!";
|
||||||
|
print $ofh $upsData;
|
||||||
|
close $ofh;
|
||||||
|
_debug(__LINE__, "Wrote ups data to $ups_state_file");
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
_debug(__LINE__, "Error writing ups data: $@");
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep 1 unless $shutdown;
|
||||||
|
}
|
||||||
|
_debug(__LINE__, "UPS collector shutting down");
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _get_ups_status {
|
||||||
|
my ($ups_name) = @_;
|
||||||
|
|
||||||
|
# upsc upsname[@hostname[:port]]
|
||||||
|
my $cmd = "upsc $ups_name 2>/dev/null";
|
||||||
|
|
||||||
|
_debug(__LINE__, "Running command: $cmd");
|
||||||
|
|
||||||
|
# Execute command and capture output
|
||||||
|
my $output = `$cmd`;
|
||||||
|
|
||||||
|
_debug(__LINE__, "upsc command output collected");
|
||||||
|
|
||||||
|
my $exit_code = $? >> 8;
|
||||||
|
|
||||||
|
_debug(__LINE__, "upsc command exited with code $exit_code");
|
||||||
|
|
||||||
|
if ($exit_code != 0) {
|
||||||
|
_debug(__LINE__, "upsc command failed with exit code $exit_code");
|
||||||
|
return encode_json({ error => "Failed to execute upsc for $ups_name" });
|
||||||
|
}
|
||||||
|
|
||||||
|
_debug(__LINE__, "upsc command executed successfully");
|
||||||
|
|
||||||
|
# Convert upsc output to nested hash structure
|
||||||
|
my $ups_data = _parse_upsc_output($output);
|
||||||
|
|
||||||
|
_debug(__LINE__, "Parsed upsc output for $ups_name");
|
||||||
|
|
||||||
|
# Check if we got any data
|
||||||
|
unless (keys %$ups_data) {
|
||||||
|
_debug(__LINE__, "No data received from upsc for $ups_name");
|
||||||
|
return encode_json({ error => "No data from UPS $ups_name" });
|
||||||
|
}
|
||||||
|
|
||||||
|
# Wrap in UPS name structure
|
||||||
|
my $result = {
|
||||||
|
$ups_name => $ups_data
|
||||||
|
};
|
||||||
|
|
||||||
|
_debug(__LINE__, "Successfully parsed UPS data for $ups_name");
|
||||||
|
|
||||||
|
# Return as pretty JSON
|
||||||
|
return JSON->new->pretty->canonical->encode($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _parse_upsc_output {
|
||||||
|
my ($output) = @_;
|
||||||
|
|
||||||
|
my $ups_data = {};
|
||||||
|
|
||||||
|
_debug(__LINE__, "Parsing upsc output");
|
||||||
|
|
||||||
|
eval {
|
||||||
|
foreach my $line (split /\n/, $output) {
|
||||||
|
# Skip empty lines and SSL init message
|
||||||
|
next if $line =~ /^\s*$/;
|
||||||
|
next if $line =~ /^Init SSL/;
|
||||||
|
|
||||||
|
# Parse key-value pairs (format: "key: value")
|
||||||
|
if ($line =~ /^([^:]+):\s*(.*)$/) {
|
||||||
|
my ($key, $value) = ($1, $2);
|
||||||
|
|
||||||
|
# Trim whitespace
|
||||||
|
$key =~ s/^\s+|\s+$//g;
|
||||||
|
$value =~ s/^\s+|\s+$//g;
|
||||||
|
|
||||||
|
# Store as flat key-value pairs (no nesting)
|
||||||
|
# Convert numeric values to numbers, keep strings as strings
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Supporting functions
|
# Supporting functions
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
# Check if a process is alive
|
|
||||||
sub _is_process_alive {
|
sub _is_process_alive {
|
||||||
my ($pid) = @_;
|
my ($pid) = @_;
|
||||||
return -d "/proc/$pid";
|
return -d "/proc/$pid";
|
||||||
@ -1226,6 +1359,38 @@ sub get_sensors_stats {
|
|||||||
return $sensors_data;
|
return $sensors_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub get_ups_stats {
|
||||||
|
_debug(__LINE__, "get_ups_stats called");
|
||||||
|
|
||||||
|
# Start PVE Mod
|
||||||
|
_pve_mod_starter();
|
||||||
|
|
||||||
|
unless (-f $ups_state_file) {
|
||||||
|
_debug(__LINE__, "UPS state file does not exist: $ups_state_file");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $ups_data;
|
||||||
|
eval {
|
||||||
|
open my $fh, '<', $ups_state_file or die "Failed to open $ups_state_file: $!";
|
||||||
|
local $/;
|
||||||
|
my $json = <$fh>;
|
||||||
|
close($fh);
|
||||||
|
$ups_data = $json;
|
||||||
|
_debug(__LINE__, "Read UPS data, JSON length: " . length($json) . " bytes");
|
||||||
|
_debug(__LINE__, "Read UPS data from $ups_state_file");
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
_debug(__LINE__, "Failed to read/parse UPS data: $@");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Notify pve_mod_worker of activity
|
||||||
|
_notify_pve_mod_worker();
|
||||||
|
|
||||||
|
return $ups_data;
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Main Collector
|
# Main Collector
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -1241,7 +1406,7 @@ sub _start_graphics_collectors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Read existing collectors from lock file
|
# Read existing collectors from lock file
|
||||||
my %existing_collectors = _read_collector_lock($lock_file);
|
my %existing_collectors = _read_collector_lock($collectors_pid_file);
|
||||||
|
|
||||||
# Generalized device collector management for future AMD/NVIDIA support
|
# Generalized device collector management for future AMD/NVIDIA support
|
||||||
my @all_devices;
|
my @all_devices;
|
||||||
@ -1307,7 +1472,7 @@ sub _start_graphics_collectors {
|
|||||||
my $device_name = $device->{card} // $device->{name} // "device$i";
|
my $device_name = $device->{card} // $device->{name} // "device$i";
|
||||||
|
|
||||||
# Check if collector already running
|
# Check if collector already running
|
||||||
my $existing_pid = _is_collector_running($device_name, $lock_file);
|
my $existing_pid = _is_collector_running($device_name, $collectors_pid_file);
|
||||||
if ($existing_pid) {
|
if ($existing_pid) {
|
||||||
_debug(__LINE__, "Collector for $type $device_name already running with PID $existing_pid");
|
_debug(__LINE__, "Collector for $type $device_name already running with PID $existing_pid");
|
||||||
push @collector_entries, [$existing_pid, $device_name, $type];
|
push @collector_entries, [$existing_pid, $device_name, $type];
|
||||||
@ -1322,7 +1487,7 @@ sub _start_graphics_collectors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Write all collector PIDs to lock file
|
# Write all collector PIDs to lock file
|
||||||
unless (_write_collector_lock($lock_file, @collector_entries)) {
|
unless (_write_collector_lock($collectors_pid_file, @collector_entries)) {
|
||||||
_debug(__LINE__, "Failed to write lock file, terminating collectors");
|
_debug(__LINE__, "Failed to write lock file, terminating collectors");
|
||||||
foreach my $entry (@collector_entries) {
|
foreach my $entry (@collector_entries) {
|
||||||
kill 'TERM', $entry->[0];
|
kill 'TERM', $entry->[0];
|
||||||
@ -1363,7 +1528,7 @@ sub _start_sensors_collector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check if already running
|
# Check if already running
|
||||||
my $existing_pid = _is_collector_running('sensors', $lock_file);
|
my $existing_pid = _is_collector_running('sensors', $collectors_pid_file);
|
||||||
if ($existing_pid) {
|
if ($existing_pid) {
|
||||||
_debug(__LINE__, "Sensors collector already running with PID $existing_pid");
|
_debug(__LINE__, "Sensors collector already running with PID $existing_pid");
|
||||||
return;
|
return;
|
||||||
@ -1375,7 +1540,7 @@ sub _start_sensors_collector {
|
|||||||
if ($pid) {
|
if ($pid) {
|
||||||
# Read existing entries
|
# Read existing entries
|
||||||
my @collector_entries;
|
my @collector_entries;
|
||||||
my %existing = _read_collector_lock($lock_file);
|
my %existing = _read_collector_lock($collectors_pid_file);
|
||||||
foreach my $name (keys %existing) {
|
foreach my $name (keys %existing) {
|
||||||
push @collector_entries, [$existing{$name}->{pid}, $name, $existing{$name}->{type}];
|
push @collector_entries, [$existing{$name}->{pid}, $name, $existing{$name}->{type}];
|
||||||
}
|
}
|
||||||
@ -1384,7 +1549,7 @@ sub _start_sensors_collector {
|
|||||||
push @collector_entries, [$pid, 'sensors', 'sensors'];
|
push @collector_entries, [$pid, 'sensors', 'sensors'];
|
||||||
|
|
||||||
# Write updated lock file
|
# Write updated lock file
|
||||||
unless (_write_collector_lock($lock_file, @collector_entries)) {
|
unless (_write_collector_lock($collectors_pid_file, @collector_entries)) {
|
||||||
_debug(__LINE__, "Failed to update lock file, terminating sensors collector");
|
_debug(__LINE__, "Failed to update lock file, terminating sensors collector");
|
||||||
kill 'TERM', $pid;
|
kill 'TERM', $pid;
|
||||||
return;
|
return;
|
||||||
@ -1400,6 +1565,59 @@ sub _start_sensors_collector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub _start_ups_collector {
|
||||||
|
|
||||||
|
if ($ups_enabled == 0) {
|
||||||
|
_debug(__LINE__, "UPS support not enabled, skipping collector startup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_debug(__LINE__, "Starting UPS collector");
|
||||||
|
|
||||||
|
# Check if UPS is configured
|
||||||
|
unless (defined $ups_device && $ups_device->{ups_name}) {
|
||||||
|
_debug(__LINE__, "No UPS configured, skipping collector startup");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if already running
|
||||||
|
my $existing_pid = _is_collector_running('ups', $collectors_pid_file);
|
||||||
|
if ($existing_pid) {
|
||||||
|
_debug(__LINE__, "UPS collector already running with PID $existing_pid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Start the collector
|
||||||
|
my $pid = _start_child_collector('ups', \&_collector_for_ups, $ups_device);
|
||||||
|
|
||||||
|
if ($pid) {
|
||||||
|
# Read existing entries
|
||||||
|
my @collector_entries;
|
||||||
|
my %existing = _read_collector_lock($collectors_pid_file);
|
||||||
|
foreach my $name (keys %existing) {
|
||||||
|
push @collector_entries, [$existing{$name}->{pid}, $name, $existing{$name}->{type}];
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add UPS entry
|
||||||
|
push @collector_entries, [$pid, 'ups', 'ups'];
|
||||||
|
|
||||||
|
# Write updated lock file
|
||||||
|
unless (_write_collector_lock($collectors_pid_file, @collector_entries)) {
|
||||||
|
_debug(__LINE__, "Failed to update lock file, terminating UPS collector");
|
||||||
|
kill 'TERM', $pid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify it's alive
|
||||||
|
sleep 0.1;
|
||||||
|
if (kill(0, $pid)) {
|
||||||
|
_debug(__LINE__, "Verified UPS collector (PID $pid) is alive");
|
||||||
|
} else {
|
||||||
|
_debug(__LINE__, "WARNING - UPS collector (PID $pid) died immediately!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# PVE Mod Worker
|
# PVE Mod Worker
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@ -1418,12 +1636,22 @@ sub _pve_mod_starter {
|
|||||||
# Ensure directory exists
|
# Ensure directory exists
|
||||||
_ensure_pve_mod_directory_exists();
|
_ensure_pve_mod_directory_exists();
|
||||||
|
|
||||||
# Try to acquire startup lock FIRST (prevents race conditions)
|
# Try to get the lock
|
||||||
_debug(__LINE__, "Trying to acquire startup lock: $startup_lock");
|
_debug(__LINE__, "Trying to acquire startup lock: $startup_lock");
|
||||||
my $startup_fh = _acquire_exclusive_lock($startup_lock, 'startup lock');
|
my $startup_fh = _acquire_exclusive_lock($startup_lock, 'startup lock');
|
||||||
return unless $startup_fh;
|
return unless $startup_fh;
|
||||||
print $startup_fh "$$\n";
|
|
||||||
|
# SECOND CHECK (after lock) - verify nothing changed while waiting
|
||||||
|
if (_is_pve_mod_worker_running()) {
|
||||||
|
_debug(__LINE__, "Worker started by another process while we waited for lock");
|
||||||
close($startup_fh);
|
close($startup_fh);
|
||||||
|
unlink($startup_lock);
|
||||||
|
return "already running";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Now we KNOW we're the only one starting things
|
||||||
|
print $startup_fh "$$\n";
|
||||||
|
$startup_fh->flush();
|
||||||
_debug(__LINE__, "Wrote PID, $$, to startup lock");
|
_debug(__LINE__, "Wrote PID, $$, to startup lock");
|
||||||
|
|
||||||
# Start sensors collector first
|
# Start sensors collector first
|
||||||
@ -1432,6 +1660,9 @@ sub _pve_mod_starter {
|
|||||||
# Start graphics collectors
|
# Start graphics collectors
|
||||||
_start_graphics_collectors();
|
_start_graphics_collectors();
|
||||||
|
|
||||||
|
# Start UPS collector
|
||||||
|
_start_ups_collector();
|
||||||
|
|
||||||
_debug(__LINE__, "All collectors started");
|
_debug(__LINE__, "All collectors started");
|
||||||
|
|
||||||
# Start pve mod worker
|
# Start pve mod worker
|
||||||
@ -1444,10 +1675,6 @@ sub _pve_mod_starter {
|
|||||||
_debug(__LINE__, "pve_mod_worker started successfully, returning");
|
_debug(__LINE__, "pve_mod_worker started successfully, returning");
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# PVE Mod Worker
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
sub _pve_mod_worker {
|
sub _pve_mod_worker {
|
||||||
_debug(__LINE__, "_pve_mod_worker called");
|
_debug(__LINE__, "_pve_mod_worker called");
|
||||||
|
|
||||||
@ -1577,7 +1804,7 @@ sub _stop_collectors {
|
|||||||
|
|
||||||
# Read current PIDs from lock file
|
# Read current PIDs from lock file
|
||||||
my @pids;
|
my @pids;
|
||||||
if (open my $lock_fh, '<', $lock_file) {
|
if (open my $lock_fh, '<', $collectors_pid_file) {
|
||||||
while (my $line = <$lock_fh>) {
|
while (my $line = <$lock_fh>) {
|
||||||
chomp $line;
|
chomp $line;
|
||||||
# Extract PID from "PID card type" format
|
# Extract PID from "PID card type" format
|
||||||
@ -1625,8 +1852,8 @@ sub _stop_collectors {
|
|||||||
if (-f $state_file) {
|
if (-f $state_file) {
|
||||||
unlink $state_file or _debug(__LINE__, "Failed to remove $state_file: $!");
|
unlink $state_file or _debug(__LINE__, "Failed to remove $state_file: $!");
|
||||||
}
|
}
|
||||||
if (-f $lock_file) {
|
if (-f $collectors_pid_file) {
|
||||||
unlink $lock_file or _debug(__LINE__, "Failed to remove $lock_file: $!");
|
unlink $collectors_pid_file or _debug(__LINE__, "Failed to remove $collectors_pid_file: $!");
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove pve mod worker directory and all files if it exists
|
# Remove pve mod worker directory and all files if it exists
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user