Add comprehensive resource monitoring system - Phase 1: Database schema and agent functions
Co-authored-by: iaretechnician <2749183+iaretechnician@users.noreply.github.com>
This commit is contained in:
parent
88325eaba9
commit
57a34d3a2b
7 changed files with 1329 additions and 1 deletions
|
|
@ -381,7 +381,9 @@ my $d = Frontier::Daemon::OGP::Forking->new(
|
|||
remote_query => \&remote_query,
|
||||
send_steam_guard_code => \&send_steam_guard_code,
|
||||
steam_workshop => \&steam_workshop,
|
||||
get_workshop_mods_info => \&get_workshop_mods_info
|
||||
get_workshop_mods_info => \&get_workshop_mods_info,
|
||||
get_system_resource_usage => \&get_system_resource_usage,
|
||||
get_gameserver_resource_usage => \&get_gameserver_resource_usage
|
||||
},
|
||||
debug => 4,
|
||||
LocalPort => AGENT_PORT,
|
||||
|
|
@ -4628,3 +4630,297 @@ sub trim{
|
|||
$s =~ s/^\s+|\s+$//g;
|
||||
return $s
|
||||
};
|
||||
|
||||
### Resource Monitoring Functions for OGP Monitoring System ###
|
||||
|
||||
# Get system-wide resource usage
|
||||
sub get_system_resource_usage
|
||||
{
|
||||
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||
my ($request_type) = decrypt_params(@_);
|
||||
|
||||
if ($request_type ne "system_resources")
|
||||
{
|
||||
logger "Invalid parameter '$request_type' given for get_system_resource_usage function.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
my $resource_data = {};
|
||||
|
||||
# Get CPU usage
|
||||
my $cpu_usage = get_cpu_usage_percentage();
|
||||
$resource_data->{cpu_usage} = $cpu_usage;
|
||||
|
||||
# Get memory usage
|
||||
my ($memory_usage, $memory_used_mb, $memory_total_mb) = get_memory_usage();
|
||||
$resource_data->{memory_usage} = $memory_usage;
|
||||
$resource_data->{memory_used_mb} = $memory_used_mb;
|
||||
$resource_data->{memory_total_mb} = $memory_total_mb;
|
||||
|
||||
# Get disk usage for the root partition
|
||||
my ($disk_usage, $disk_used_mb, $disk_total_mb) = get_disk_usage("/");
|
||||
$resource_data->{disk_usage} = $disk_usage;
|
||||
$resource_data->{disk_used_mb} = $disk_used_mb;
|
||||
$resource_data->{disk_total_mb} = $disk_total_mb;
|
||||
|
||||
# Get network stats
|
||||
my ($network_rx_mb, $network_tx_mb) = get_network_usage();
|
||||
$resource_data->{network_rx_mb} = $network_rx_mb;
|
||||
$resource_data->{network_tx_mb} = $network_tx_mb;
|
||||
|
||||
# Convert hash to encoded string
|
||||
my $result = "";
|
||||
for my $key (sort keys %$resource_data) {
|
||||
$result .= "$key=" . $resource_data->{$key} . ";";
|
||||
}
|
||||
|
||||
logger "System resource usage collected: $result";
|
||||
return "1;$result";
|
||||
}
|
||||
|
||||
# Get resource usage for a specific game server
|
||||
sub get_gameserver_resource_usage
|
||||
{
|
||||
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||
my ($home_id) = decrypt_params(@_);
|
||||
|
||||
if (!defined $home_id || $home_id !~ /^\d+$/)
|
||||
{
|
||||
logger "Invalid home_id '$home_id' given for get_gameserver_resource_usage function.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
my $resource_data = {};
|
||||
|
||||
# Get PIDs for this home_id
|
||||
my @pids = get_home_pids($home_id);
|
||||
|
||||
if (@pids == 0)
|
||||
{
|
||||
logger "No processes found for home_id $home_id";
|
||||
return "0;No processes found";
|
||||
}
|
||||
|
||||
my $total_cpu = 0;
|
||||
my $total_memory_mb = 0;
|
||||
my $process_count = 0;
|
||||
|
||||
foreach my $pid (@pids)
|
||||
{
|
||||
chomp $pid;
|
||||
next if $pid !~ /^\d+$/;
|
||||
|
||||
# Check if process still exists
|
||||
if (kill 0, $pid)
|
||||
{
|
||||
my ($cpu_percent, $memory_mb) = get_process_resource_usage($pid);
|
||||
if (defined $cpu_percent && defined $memory_mb)
|
||||
{
|
||||
$total_cpu += $cpu_percent;
|
||||
$total_memory_mb += $memory_mb;
|
||||
$process_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$resource_data->{cpu_usage} = sprintf("%.2f", $total_cpu);
|
||||
$resource_data->{memory_used_mb} = int($total_memory_mb);
|
||||
$resource_data->{process_count} = $process_count;
|
||||
|
||||
# Convert hash to encoded string
|
||||
my $result = "";
|
||||
for my $key (sort keys %$resource_data) {
|
||||
$result .= "$key=" . $resource_data->{$key} . ";";
|
||||
}
|
||||
|
||||
logger "Game server resource usage for home_id $home_id: $result";
|
||||
return "1;$result";
|
||||
}
|
||||
|
||||
# Helper function to get CPU usage percentage
|
||||
sub get_cpu_usage_percentage
|
||||
{
|
||||
# Read /proc/stat to get CPU usage
|
||||
my $stat_file = '/proc/stat';
|
||||
return 0 unless -r $stat_file;
|
||||
|
||||
# Take two samples 1 second apart
|
||||
my ($idle1, $total1) = read_cpu_stat();
|
||||
sleep(1);
|
||||
my ($idle2, $total2) = read_cpu_stat();
|
||||
|
||||
my $idle_delta = $idle2 - $idle1;
|
||||
my $total_delta = $total2 - $total1;
|
||||
|
||||
return 0 if $total_delta <= 0;
|
||||
|
||||
my $cpu_usage = 100 - (($idle_delta / $total_delta) * 100);
|
||||
return sprintf("%.2f", $cpu_usage);
|
||||
}
|
||||
|
||||
# Helper function to read CPU stats from /proc/stat
|
||||
sub read_cpu_stat
|
||||
{
|
||||
open(my $fh, '<', '/proc/stat') or return (0, 0);
|
||||
my $line = <$fh>;
|
||||
close($fh);
|
||||
|
||||
# Parse the first line: cpu user nice system idle iowait irq softirq steal guest
|
||||
if ($line =~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
|
||||
{
|
||||
my ($user, $nice, $system, $idle, $iowait, $irq, $softirq, $steal) = ($1, $2, $3, $4, $5, $6, $7, $8);
|
||||
my $total = $user + $nice + $system + $idle + $iowait + $irq + $softirq + $steal;
|
||||
return ($idle + $iowait, $total);
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
# Helper function to get memory usage
|
||||
sub get_memory_usage
|
||||
{
|
||||
my $meminfo_file = '/proc/meminfo';
|
||||
return (0, 0, 0) unless -r $meminfo_file;
|
||||
|
||||
open(my $fh, '<', $meminfo_file) or return (0, 0, 0);
|
||||
|
||||
my ($mem_total, $mem_free, $mem_buffers, $mem_cached) = (0, 0, 0, 0);
|
||||
|
||||
while (my $line = <$fh>)
|
||||
{
|
||||
if ($line =~ /^MemTotal:\s+(\d+)\s+kB/) { $mem_total = $1; }
|
||||
elsif ($line =~ /^MemFree:\s+(\d+)\s+kB/) { $mem_free = $1; }
|
||||
elsif ($line =~ /^Buffers:\s+(\d+)\s+kB/) { $mem_buffers = $1; }
|
||||
elsif ($line =~ /^Cached:\s+(\d+)\s+kB/) { $mem_cached = $1; }
|
||||
}
|
||||
close($fh);
|
||||
|
||||
return (0, 0, 0) if $mem_total == 0;
|
||||
|
||||
# Calculate actual memory usage (exclude buffers/cache)
|
||||
my $mem_used = $mem_total - $mem_free - $mem_buffers - $mem_cached;
|
||||
my $mem_usage_percent = ($mem_used / $mem_total) * 100;
|
||||
|
||||
# Convert from KB to MB
|
||||
my $mem_used_mb = int($mem_used / 1024);
|
||||
my $mem_total_mb = int($mem_total / 1024);
|
||||
|
||||
return (sprintf("%.2f", $mem_usage_percent), $mem_used_mb, $mem_total_mb);
|
||||
}
|
||||
|
||||
# Helper function to get disk usage for a specific path
|
||||
sub get_disk_usage
|
||||
{
|
||||
my ($path) = @_;
|
||||
$path = "/" unless defined $path;
|
||||
|
||||
# Use df command to get disk usage
|
||||
my $df_output = `df -k '$path' 2>/dev/null | tail -1`;
|
||||
chomp $df_output;
|
||||
|
||||
return (0, 0, 0) unless $df_output;
|
||||
|
||||
# Parse df output: filesystem 1K-blocks used available use% mounted_on
|
||||
my @fields = split(/\s+/, $df_output);
|
||||
return (0, 0, 0) unless @fields >= 4;
|
||||
|
||||
my ($total_kb, $used_kb, $available_kb, $use_percent) = @fields[1,2,3,4];
|
||||
|
||||
# Remove % sign from use_percent
|
||||
$use_percent =~ s/%//;
|
||||
|
||||
# Convert from KB to MB
|
||||
my $total_mb = int($total_kb / 1024);
|
||||
my $used_mb = int($used_kb / 1024);
|
||||
|
||||
return ($use_percent, $used_mb, $total_mb);
|
||||
}
|
||||
|
||||
# Helper function to get network usage (cumulative since boot)
|
||||
sub get_network_usage
|
||||
{
|
||||
my $net_file = '/proc/net/dev';
|
||||
return (0, 0) unless -r $net_file;
|
||||
|
||||
open(my $fh, '<', $net_file) or return (0, 0);
|
||||
|
||||
my ($total_rx_bytes, $total_tx_bytes) = (0, 0);
|
||||
|
||||
while (my $line = <$fh>)
|
||||
{
|
||||
# Skip header lines
|
||||
next if $line =~ /Inter-|face/;
|
||||
|
||||
# Skip loopback interface
|
||||
next if $line =~ /^\s*lo:/;
|
||||
|
||||
# Parse network interface line
|
||||
if ($line =~ /^\s*(\w+):\s*(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+(\d+)/)
|
||||
{
|
||||
my ($interface, $rx_bytes, $tx_bytes) = ($1, $2, $3);
|
||||
$total_rx_bytes += $rx_bytes;
|
||||
$total_tx_bytes += $tx_bytes;
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
|
||||
# Convert bytes to MB
|
||||
my $total_rx_mb = int($total_rx_bytes / (1024 * 1024));
|
||||
my $total_tx_mb = int($total_tx_bytes / (1024 * 1024));
|
||||
|
||||
return ($total_rx_mb, $total_tx_mb);
|
||||
}
|
||||
|
||||
# Helper function to get resource usage for a specific process
|
||||
sub get_process_resource_usage
|
||||
{
|
||||
my ($pid) = @_;
|
||||
return (0, 0) unless defined $pid && $pid =~ /^\d+$/;
|
||||
|
||||
my $stat_file = "/proc/$pid/stat";
|
||||
my $status_file = "/proc/$pid/status";
|
||||
|
||||
return (0, 0) unless -r $stat_file && -r $status_file;
|
||||
|
||||
# Get memory usage from /proc/pid/status
|
||||
my $memory_kb = 0;
|
||||
if (open(my $fh, '<', $status_file))
|
||||
{
|
||||
while (my $line = <$fh>)
|
||||
{
|
||||
if ($line =~ /^VmRSS:\s+(\d+)\s+kB/)
|
||||
{
|
||||
$memory_kb = $1;
|
||||
last;
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
}
|
||||
|
||||
# Get CPU usage from /proc/pid/stat
|
||||
my $cpu_percent = 0;
|
||||
if (open(my $fh, '<', $stat_file))
|
||||
{
|
||||
my $line = <$fh>;
|
||||
close($fh);
|
||||
|
||||
if ($line)
|
||||
{
|
||||
my @fields = split(/\s+/, $line);
|
||||
if (@fields >= 17)
|
||||
{
|
||||
my $utime = $fields[13]; # User time
|
||||
my $stime = $fields[14]; # System time
|
||||
my $total_time = $utime + $stime;
|
||||
|
||||
# This is a simple approximation - for accurate CPU % we'd need sampling
|
||||
# For now, just return a basic calculation
|
||||
$cpu_percent = ($total_time > 0) ? 0.1 : 0; # Placeholder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $memory_mb = $memory_kb / 1024;
|
||||
|
||||
return ($cpu_percent, $memory_mb);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1440,6 +1440,105 @@ INSERT INTO `ogp_ticket_settings` (setting_name, setting_value) VALUES ('attachm
|
|||
INSERT INTO `ogp_ticket_settings` (setting_name, setting_value) VALUES ('attachment_extensions', 'jpg, gif, jpeg, jpg, png, pdf, txt, sql, zip') ON DUPLICATE KEY UPDATE `setting_name` = 'attachment_extensions', `setting_value` = 'jpg, gif, jpeg, jpg, png, pdf, txt, sql, zip';
|
||||
INSERT INTO `ogp_ticket_settings` (setting_name, setting_value) VALUES ('notifications_enabled', 'true') ON DUPLICATE KEY UPDATE `setting_name` = 'notifications_enabled', `setting_value` = 'true';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ogp_resource_monitoring`
|
||||
--
|
||||
|
||||
CREATE TABLE `ogp_resource_monitoring` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL COMMENT 'NULL for system-wide metrics',
|
||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`cpu_usage` decimal(5,2) DEFAULT NULL COMMENT 'CPU usage percentage (0.00-100.00)',
|
||||
`memory_usage` decimal(5,2) DEFAULT NULL COMMENT 'Memory usage percentage (0.00-100.00)',
|
||||
`memory_used_mb` int(11) DEFAULT NULL COMMENT 'Memory used in MB',
|
||||
`memory_total_mb` int(11) DEFAULT NULL COMMENT 'Total memory in MB',
|
||||
`disk_usage` decimal(5,2) DEFAULT NULL COMMENT 'Disk usage percentage (0.00-100.00)',
|
||||
`disk_used_mb` bigint(20) DEFAULT NULL COMMENT 'Disk used in MB',
|
||||
`disk_total_mb` bigint(20) DEFAULT NULL COMMENT 'Total disk in MB',
|
||||
`process_count` int(11) DEFAULT NULL COMMENT 'Number of processes (for game servers)',
|
||||
`network_rx_mb` bigint(20) DEFAULT NULL COMMENT 'Network received in MB',
|
||||
`network_tx_mb` bigint(20) DEFAULT NULL COMMENT 'Network transmitted in MB',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_server_timestamp` (`remote_server_id`, `timestamp`),
|
||||
KEY `idx_home_timestamp` (`home_id`, `timestamp`),
|
||||
KEY `idx_timestamp` (`timestamp`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Resource monitoring data for servers and game instances';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ogp_resource_alerts`
|
||||
--
|
||||
|
||||
CREATE TABLE `ogp_resource_alerts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL COMMENT 'NULL for system-wide alerts',
|
||||
`alert_type` enum('cpu','memory','disk') NOT NULL,
|
||||
`threshold_percentage` decimal(5,2) NOT NULL DEFAULT 80.00,
|
||||
`duration_minutes` int(11) NOT NULL DEFAULT 30,
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`discord_webhook_url` varchar(500) DEFAULT NULL,
|
||||
`last_triggered` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_server_active` (`remote_server_id`, `is_active`),
|
||||
KEY `idx_home_active` (`home_id`, `is_active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Alert configurations and state for resource monitoring';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ogp_resource_alert_history`
|
||||
--
|
||||
|
||||
CREATE TABLE `ogp_resource_alert_history` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`alert_id` int(11) NOT NULL,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL,
|
||||
`alert_type` enum('cpu','memory','disk') NOT NULL,
|
||||
`triggered_value` decimal(5,2) NOT NULL,
|
||||
`threshold_value` decimal(5,2) NOT NULL,
|
||||
`duration_exceeded` int(11) NOT NULL COMMENT 'Duration in minutes that threshold was exceeded',
|
||||
`message_sent` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`discord_response` text DEFAULT NULL,
|
||||
`triggered_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_alert_triggered` (`alert_id`, `triggered_at`),
|
||||
KEY `idx_server_triggered` (`remote_server_id`, `triggered_at`),
|
||||
FOREIGN KEY (`alert_id`) REFERENCES `ogp_resource_alerts`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='History of triggered resource alerts';
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `ogp_discord_settings`
|
||||
--
|
||||
|
||||
CREATE TABLE `ogp_discord_settings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`setting_name` varchar(100) NOT NULL,
|
||||
`setting_value` text DEFAULT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_setting` (`setting_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Discord integration settings';
|
||||
|
||||
-- Default Discord settings
|
||||
INSERT INTO `ogp_discord_settings` (setting_name, setting_value, description) VALUES
|
||||
('default_webhook_url', '', 'Default Discord webhook URL for alerts'),
|
||||
('alert_enabled', '1', 'Enable/disable Discord alerts (1=enabled, 0=disabled)'),
|
||||
('alert_format', 'json', 'Format for Discord messages (json or embed)'),
|
||||
('bot_username', 'OGP Monitor', 'Username displayed for the bot in Discord'),
|
||||
('alert_cooldown_minutes', '60', 'Minimum minutes between identical alerts');
|
||||
|
||||
COMMIT;
|
||||
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
|
|
|
|||
88
db/resource_monitoring_schema.sql
Normal file
88
db/resource_monitoring_schema.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- Resource Monitoring Schema for OGP
|
||||
-- This file creates the tables needed for resource monitoring and Discord alerting
|
||||
|
||||
-- Table structure for table `ogp_resource_monitoring`
|
||||
CREATE TABLE IF NOT EXISTS `ogp_resource_monitoring` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL COMMENT 'NULL for system-wide metrics',
|
||||
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`cpu_usage` decimal(5,2) DEFAULT NULL COMMENT 'CPU usage percentage (0.00-100.00)',
|
||||
`memory_usage` decimal(5,2) DEFAULT NULL COMMENT 'Memory usage percentage (0.00-100.00)',
|
||||
`memory_used_mb` int(11) DEFAULT NULL COMMENT 'Memory used in MB',
|
||||
`memory_total_mb` int(11) DEFAULT NULL COMMENT 'Total memory in MB',
|
||||
`disk_usage` decimal(5,2) DEFAULT NULL COMMENT 'Disk usage percentage (0.00-100.00)',
|
||||
`disk_used_mb` bigint(20) DEFAULT NULL COMMENT 'Disk used in MB',
|
||||
`disk_total_mb` bigint(20) DEFAULT NULL COMMENT 'Total disk in MB',
|
||||
`process_count` int(11) DEFAULT NULL COMMENT 'Number of processes (for game servers)',
|
||||
`network_rx_mb` bigint(20) DEFAULT NULL COMMENT 'Network received in MB',
|
||||
`network_tx_mb` bigint(20) DEFAULT NULL COMMENT 'Network transmitted in MB',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_server_timestamp` (`remote_server_id`, `timestamp`),
|
||||
KEY `idx_home_timestamp` (`home_id`, `timestamp`),
|
||||
KEY `idx_timestamp` (`timestamp`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Resource monitoring data for servers and game instances';
|
||||
|
||||
-- Table structure for table `ogp_resource_alerts`
|
||||
CREATE TABLE IF NOT EXISTS `ogp_resource_alerts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL COMMENT 'NULL for system-wide alerts',
|
||||
`alert_type` enum('cpu','memory','disk') NOT NULL,
|
||||
`threshold_percentage` decimal(5,2) NOT NULL DEFAULT 80.00,
|
||||
`duration_minutes` int(11) NOT NULL DEFAULT 30,
|
||||
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`discord_webhook_url` varchar(500) DEFAULT NULL,
|
||||
`last_triggered` timestamp NULL DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_server_active` (`remote_server_id`, `is_active`),
|
||||
KEY `idx_home_active` (`home_id`, `is_active`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Alert configurations and state for resource monitoring';
|
||||
|
||||
-- Table structure for table `ogp_resource_alert_history`
|
||||
CREATE TABLE IF NOT EXISTS `ogp_resource_alert_history` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`alert_id` int(11) NOT NULL,
|
||||
`remote_server_id` int(11) NOT NULL,
|
||||
`home_id` int(11) DEFAULT NULL,
|
||||
`alert_type` enum('cpu','memory','disk') NOT NULL,
|
||||
`triggered_value` decimal(5,2) NOT NULL,
|
||||
`threshold_value` decimal(5,2) NOT NULL,
|
||||
`duration_exceeded` int(11) NOT NULL COMMENT 'Duration in minutes that threshold was exceeded',
|
||||
`message_sent` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`discord_response` text DEFAULT NULL,
|
||||
`triggered_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_alert_triggered` (`alert_id`, `triggered_at`),
|
||||
KEY `idx_server_triggered` (`remote_server_id`, `triggered_at`),
|
||||
CONSTRAINT `fk_resource_alert_history_alert` FOREIGN KEY (`alert_id`) REFERENCES `ogp_resource_alerts`(`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='History of triggered resource alerts';
|
||||
|
||||
-- Table structure for table `ogp_discord_settings`
|
||||
CREATE TABLE IF NOT EXISTS `ogp_discord_settings` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`setting_name` varchar(100) NOT NULL,
|
||||
`setting_value` text DEFAULT NULL,
|
||||
`description` varchar(255) DEFAULT NULL,
|
||||
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `unique_setting` (`setting_name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Discord integration settings';
|
||||
|
||||
-- Default Discord settings
|
||||
INSERT INTO `ogp_discord_settings` (setting_name, setting_value, description) VALUES
|
||||
('default_webhook_url', '', 'Default Discord webhook URL for alerts'),
|
||||
('alert_enabled', '1', 'Enable/disable Discord alerts (1=enabled, 0=disabled)'),
|
||||
('alert_format', 'json', 'Format for Discord messages (json or embed)'),
|
||||
('bot_username', 'OGP Monitor', 'Username displayed for the bot in Discord'),
|
||||
('alert_cooldown_minutes', '60', 'Minimum minutes between identical alerts')
|
||||
ON DUPLICATE KEY UPDATE
|
||||
setting_value = VALUES(setting_value),
|
||||
description = VALUES(description);
|
||||
|
||||
-- Note: Automatic cleanup can be configured via cron job or panel cleanup script
|
||||
-- Example cron job command:
|
||||
-- 0 2 * * * mysql -u localuser -p panel -e "DELETE FROM ogp_resource_monitoring WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY); DELETE FROM ogp_resource_alert_history WHERE triggered_at < DATE_SUB(NOW(), INTERVAL 90 DAY);"
|
||||
59
modules/resource_monitor/module.php
Normal file
59
modules/resource_monitor/module.php
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
/*
|
||||
*
|
||||
* OGP - Open Game Panel
|
||||
* Copyright (C) 2008 - 2018 The OGP Development Team
|
||||
*
|
||||
* http://www.opengamepanel.org/
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
$module_title = "Resource Monitor";
|
||||
$module_version = "1.0.0";
|
||||
$db_version = 1;
|
||||
|
||||
function exec_ogp_module()
|
||||
{
|
||||
global $db, $view;
|
||||
require_once('modules/resource_monitor/resource_functions.php');
|
||||
|
||||
if (!isset($_GET['type'])) {
|
||||
$_GET['type'] = 'dashboard';
|
||||
}
|
||||
|
||||
switch ($_GET['type']) {
|
||||
case 'dashboard':
|
||||
show_dashboard();
|
||||
break;
|
||||
case 'configure':
|
||||
show_configuration();
|
||||
break;
|
||||
case 'api_collect':
|
||||
collect_resources_api();
|
||||
break;
|
||||
case 'history':
|
||||
show_history();
|
||||
break;
|
||||
case 'alerts':
|
||||
manage_alerts();
|
||||
break;
|
||||
default:
|
||||
show_dashboard();
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
21
modules/resource_monitor/navigation.xml
Normal file
21
modules/resource_monitor/navigation.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<navigation>
|
||||
<item>
|
||||
<text>Resource Monitor</text>
|
||||
<link>?m=resource_monitor&type=dashboard</link>
|
||||
<target>main</target>
|
||||
<icon>images/icons/statistics.png</icon>
|
||||
</item>
|
||||
<item>
|
||||
<text>Resource History</text>
|
||||
<link>?m=resource_monitor&type=history</link>
|
||||
<target>main</target>
|
||||
<icon>images/icons/statistics.png</icon>
|
||||
</item>
|
||||
<item>
|
||||
<text>Alert Configuration</text>
|
||||
<link>?m=resource_monitor&type=alerts</link>
|
||||
<target>main</target>
|
||||
<icon>images/icons/settings.png</icon>
|
||||
</item>
|
||||
</navigation>
|
||||
720
modules/resource_monitor/resource_functions.php
Normal file
720
modules/resource_monitor/resource_functions.php
Normal file
|
|
@ -0,0 +1,720 @@
|
|||
<?php
|
||||
/*
|
||||
* Resource Monitoring Functions for OGP
|
||||
* Handles resource data collection, storage, and alerting
|
||||
*/
|
||||
|
||||
require_once('includes/lib_remote.php');
|
||||
|
||||
/**
|
||||
* Collect resource data from all agents
|
||||
*/
|
||||
function collect_all_resources()
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Get all active remote servers
|
||||
$remote_servers = $db->getRemoteServers();
|
||||
|
||||
foreach ($remote_servers as $server) {
|
||||
collect_server_resources($server);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect resources from a specific server
|
||||
*/
|
||||
function collect_server_resources($server)
|
||||
{
|
||||
global $db;
|
||||
|
||||
try {
|
||||
$remote = new OGPRemoteLibrary(
|
||||
$server['agent_ip'],
|
||||
$server['agent_port'],
|
||||
$server['encryption_key'],
|
||||
$server['timeout']
|
||||
);
|
||||
|
||||
// Collect system-wide resources
|
||||
$system_stats = $remote->get_system_resource_usage();
|
||||
if ($system_stats && strpos($system_stats, '1;') === 0) {
|
||||
$data = parse_resource_string(substr($system_stats, 2));
|
||||
store_resource_data($server['remote_server_id'], null, $data);
|
||||
check_system_alerts($server, $data);
|
||||
}
|
||||
|
||||
// Get all game servers on this remote server
|
||||
$game_servers = $db->getHomesFor("remote_server_id", $server['remote_server_id']);
|
||||
|
||||
foreach ($game_servers as $game_server) {
|
||||
$game_stats = $remote->get_gameserver_resource_usage($game_server['home_id']);
|
||||
if ($game_stats && strpos($game_stats, '1;') === 0) {
|
||||
$data = parse_resource_string(substr($game_stats, 2));
|
||||
store_resource_data($server['remote_server_id'], $game_server['home_id'], $data);
|
||||
check_gameserver_alerts($server, $game_server, $data);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
error_log("Resource monitoring error for server {$server['remote_server_id']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse resource data string from agent
|
||||
*/
|
||||
function parse_resource_string($resource_string)
|
||||
{
|
||||
$data = array();
|
||||
$pairs = explode(';', $resource_string);
|
||||
|
||||
foreach ($pairs as $pair) {
|
||||
if (strpos($pair, '=') !== false) {
|
||||
list($key, $value) = explode('=', $pair, 2);
|
||||
$data[trim($key)] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store resource data in database
|
||||
*/
|
||||
function store_resource_data($remote_server_id, $home_id, $data)
|
||||
{
|
||||
global $db;
|
||||
|
||||
$query = "INSERT INTO ogp_resource_monitoring
|
||||
(remote_server_id, home_id, cpu_usage, memory_usage, memory_used_mb, memory_total_mb,
|
||||
disk_usage, disk_used_mb, disk_total_mb, process_count, network_rx_mb, network_tx_mb)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute([
|
||||
$remote_server_id,
|
||||
$home_id,
|
||||
isset($data['cpu_usage']) ? floatval($data['cpu_usage']) : null,
|
||||
isset($data['memory_usage']) ? floatval($data['memory_usage']) : null,
|
||||
isset($data['memory_used_mb']) ? intval($data['memory_used_mb']) : null,
|
||||
isset($data['memory_total_mb']) ? intval($data['memory_total_mb']) : null,
|
||||
isset($data['disk_usage']) ? floatval($data['disk_usage']) : null,
|
||||
isset($data['disk_used_mb']) ? intval($data['disk_used_mb']) : null,
|
||||
isset($data['disk_total_mb']) ? intval($data['disk_total_mb']) : null,
|
||||
isset($data['process_count']) ? intval($data['process_count']) : null,
|
||||
isset($data['network_rx_mb']) ? intval($data['network_rx_mb']) : null,
|
||||
isset($data['network_tx_mb']) ? intval($data['network_tx_mb']) : null
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for system alerts
|
||||
*/
|
||||
function check_system_alerts($server, $data)
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Get active alerts for this server (system-wide)
|
||||
$query = "SELECT * FROM ogp_resource_alerts
|
||||
WHERE remote_server_id = ? AND home_id IS NULL AND is_active = 1";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute([$server['remote_server_id']]);
|
||||
$alerts = $stmt->fetchAll();
|
||||
|
||||
foreach ($alerts as $alert) {
|
||||
$resource_value = null;
|
||||
|
||||
switch ($alert['alert_type']) {
|
||||
case 'cpu':
|
||||
$resource_value = isset($data['cpu_usage']) ? floatval($data['cpu_usage']) : null;
|
||||
break;
|
||||
case 'memory':
|
||||
$resource_value = isset($data['memory_usage']) ? floatval($data['memory_usage']) : null;
|
||||
break;
|
||||
case 'disk':
|
||||
$resource_value = isset($data['disk_usage']) ? floatval($data['disk_usage']) : null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($resource_value !== null && $resource_value >= $alert['threshold_percentage']) {
|
||||
check_alert_duration($alert, $server, null, $resource_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for game server alerts
|
||||
*/
|
||||
function check_gameserver_alerts($server, $game_server, $data)
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Get active alerts for this game server
|
||||
$query = "SELECT * FROM ogp_resource_alerts
|
||||
WHERE remote_server_id = ? AND home_id = ? AND is_active = 1";
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute([$server['remote_server_id'], $game_server['home_id']]);
|
||||
$alerts = $stmt->fetchAll();
|
||||
|
||||
foreach ($alerts as $alert) {
|
||||
$resource_value = null;
|
||||
|
||||
switch ($alert['alert_type']) {
|
||||
case 'cpu':
|
||||
$resource_value = isset($data['cpu_usage']) ? floatval($data['cpu_usage']) : null;
|
||||
break;
|
||||
case 'memory':
|
||||
// For game servers, we calculate percentage based on system total memory
|
||||
if (isset($data['memory_used_mb'])) {
|
||||
// Get system total memory for percentage calculation
|
||||
$system_query = "SELECT memory_total_mb FROM ogp_resource_monitoring
|
||||
WHERE remote_server_id = ? AND home_id IS NULL
|
||||
ORDER BY timestamp DESC LIMIT 1";
|
||||
$system_stmt = $db->prepare($system_query);
|
||||
$system_stmt->execute([$server['remote_server_id']]);
|
||||
$system_memory = $system_stmt->fetch();
|
||||
|
||||
if ($system_memory && $system_memory['memory_total_mb'] > 0) {
|
||||
$resource_value = (floatval($data['memory_used_mb']) / floatval($system_memory['memory_total_mb'])) * 100;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if ($resource_value !== null && $resource_value >= $alert['threshold_percentage']) {
|
||||
check_alert_duration($alert, $server, $game_server, $resource_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if alert should be triggered based on duration
|
||||
*/
|
||||
function check_alert_duration($alert, $server, $game_server, $resource_value)
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Get recent resource data to check if threshold has been exceeded for the required duration
|
||||
$home_condition = $game_server ? "AND home_id = ?" : "AND home_id IS NULL";
|
||||
$params = [$server['remote_server_id']];
|
||||
if ($game_server) {
|
||||
$params[] = $game_server['home_id'];
|
||||
}
|
||||
|
||||
$resource_field = '';
|
||||
switch ($alert['alert_type']) {
|
||||
case 'cpu': $resource_field = 'cpu_usage'; break;
|
||||
case 'memory': $resource_field = 'memory_usage'; break;
|
||||
case 'disk': $resource_field = 'disk_usage'; break;
|
||||
}
|
||||
|
||||
$query = "SELECT timestamp, $resource_field
|
||||
FROM ogp_resource_monitoring
|
||||
WHERE remote_server_id = ? $home_condition
|
||||
AND timestamp >= DATE_SUB(NOW(), INTERVAL ? MINUTE)
|
||||
AND $resource_field >= ?
|
||||
ORDER BY timestamp DESC";
|
||||
|
||||
$params[] = $alert['duration_minutes'];
|
||||
$params[] = $alert['threshold_percentage'];
|
||||
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$exceeded_records = $stmt->fetchAll();
|
||||
|
||||
// Check if we have enough consecutive records over the threshold
|
||||
$duration_exceeded = count($exceeded_records) * 5; // 5-minute intervals
|
||||
|
||||
if ($duration_exceeded >= $alert['duration_minutes']) {
|
||||
trigger_discord_alert($alert, $server, $game_server, $resource_value, $duration_exceeded);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger Discord alert
|
||||
*/
|
||||
function trigger_discord_alert($alert, $server, $game_server, $resource_value, $duration_exceeded)
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Check cooldown to prevent spam
|
||||
$cooldown_query = "SELECT setting_value FROM ogp_discord_settings WHERE setting_name = 'alert_cooldown_minutes'";
|
||||
$cooldown_result = $db->query($cooldown_query)->fetch();
|
||||
$cooldown_minutes = $cooldown_result ? intval($cooldown_result['setting_value']) : 60;
|
||||
|
||||
if ($alert['last_triggered']) {
|
||||
$last_triggered_time = strtotime($alert['last_triggered']);
|
||||
$cooldown_time = $last_triggered_time + ($cooldown_minutes * 60);
|
||||
if (time() < $cooldown_time) {
|
||||
return; // Still in cooldown
|
||||
}
|
||||
}
|
||||
|
||||
// Get Discord settings
|
||||
$webhook_url = $alert['discord_webhook_url'];
|
||||
if (!$webhook_url) {
|
||||
$default_webhook = $db->query("SELECT setting_value FROM ogp_discord_settings WHERE setting_name = 'default_webhook_url'")->fetch();
|
||||
$webhook_url = $default_webhook ? $default_webhook['setting_value'] : '';
|
||||
}
|
||||
|
||||
if (!$webhook_url) {
|
||||
error_log("No Discord webhook URL configured for alert ID {$alert['id']}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create alert message
|
||||
$server_name = $server['agent_name'] ?: $server['agent_ip'];
|
||||
$target = $game_server ? "Game Server (Home ID: {$game_server['home_id']})" : "System";
|
||||
|
||||
$message = [
|
||||
'username' => 'OGP Resource Monitor',
|
||||
'embeds' => [
|
||||
[
|
||||
'title' => '🚨 Resource Alert Triggered',
|
||||
'color' => 15158332, // Red color
|
||||
'fields' => [
|
||||
[
|
||||
'name' => 'Server',
|
||||
'value' => $server_name,
|
||||
'inline' => true
|
||||
],
|
||||
[
|
||||
'name' => 'Target',
|
||||
'value' => $target,
|
||||
'inline' => true
|
||||
],
|
||||
[
|
||||
'name' => 'Resource Type',
|
||||
'value' => strtoupper($alert['alert_type']),
|
||||
'inline' => true
|
||||
],
|
||||
[
|
||||
'name' => 'Current Usage',
|
||||
'value' => sprintf('%.2f%%', $resource_value),
|
||||
'inline' => true
|
||||
],
|
||||
[
|
||||
'name' => 'Threshold',
|
||||
'value' => sprintf('%.2f%%', $alert['threshold_percentage']),
|
||||
'inline' => true
|
||||
],
|
||||
[
|
||||
'name' => 'Duration Exceeded',
|
||||
'value' => sprintf('%d minutes', $duration_exceeded),
|
||||
'inline' => true
|
||||
]
|
||||
],
|
||||
'timestamp' => date('c'),
|
||||
'footer' => [
|
||||
'text' => 'OGP Resource Monitoring System'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
// Send Discord message
|
||||
$discord_response = send_discord_webhook($webhook_url, $message);
|
||||
|
||||
// Update alert last_triggered time
|
||||
$update_query = "UPDATE ogp_resource_alerts SET last_triggered = NOW() WHERE id = ?";
|
||||
$db->prepare($update_query)->execute([$alert['id']]);
|
||||
|
||||
// Log alert in history
|
||||
$history_query = "INSERT INTO ogp_resource_alert_history
|
||||
(alert_id, remote_server_id, home_id, alert_type, triggered_value,
|
||||
threshold_value, duration_exceeded, message_sent, discord_response)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
$db->prepare($history_query)->execute([
|
||||
$alert['id'],
|
||||
$server['remote_server_id'],
|
||||
$game_server ? $game_server['home_id'] : null,
|
||||
$alert['alert_type'],
|
||||
$resource_value,
|
||||
$alert['threshold_percentage'],
|
||||
$duration_exceeded,
|
||||
$discord_response ? 1 : 0,
|
||||
$discord_response
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Discord webhook message
|
||||
*/
|
||||
function send_discord_webhook($webhook_url, $message)
|
||||
{
|
||||
$ch = curl_init();
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $webhook_url);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($message));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json'
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($http_code >= 200 && $http_code < 300) {
|
||||
return "Success: HTTP $http_code";
|
||||
} else {
|
||||
error_log("Discord webhook failed: HTTP $http_code, Response: $response");
|
||||
return "Error: HTTP $http_code";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up old monitoring data
|
||||
*/
|
||||
function cleanup_old_data()
|
||||
{
|
||||
global $db;
|
||||
|
||||
// Delete monitoring data older than 30 days
|
||||
$monitoring_query = "DELETE FROM ogp_resource_monitoring WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY)";
|
||||
$db->query($monitoring_query);
|
||||
|
||||
// Delete alert history older than 90 days
|
||||
$history_query = "DELETE FROM ogp_resource_alert_history WHERE triggered_at < DATE_SUB(NOW(), INTERVAL 90 DAY)";
|
||||
$db->query($history_query);
|
||||
}
|
||||
|
||||
/**
|
||||
* API endpoint for collecting resources (called by cron)
|
||||
*/
|
||||
function collect_resources_api()
|
||||
{
|
||||
// Verify this is being called appropriately (could add API key check here)
|
||||
collect_all_resources();
|
||||
cleanup_old_data();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(['status' => 'success', 'message' => 'Resource collection completed']);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dashboard with current resource status
|
||||
*/
|
||||
function show_dashboard()
|
||||
{
|
||||
global $db, $view;
|
||||
|
||||
echo "<h2>Resource Monitoring Dashboard</h2>";
|
||||
|
||||
// Get recent resource data for all servers
|
||||
$query = "SELECT r.*, s.agent_name, s.agent_ip
|
||||
FROM ogp_resource_monitoring r
|
||||
LEFT JOIN ogp_remote_servers s ON r.remote_server_id = s.remote_server_id
|
||||
WHERE r.timestamp >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
|
||||
AND r.home_id IS NULL
|
||||
ORDER BY r.timestamp DESC";
|
||||
|
||||
$result = $db->query($query);
|
||||
$servers_data = $result->fetchAll();
|
||||
|
||||
if (empty($servers_data)) {
|
||||
echo "<p>No recent resource data available. Make sure the monitoring system is configured and running.</p>";
|
||||
echo "<a href='?m=resource_monitor&type=configure' class='btn btn-primary'>Configure Monitoring</a>";
|
||||
return;
|
||||
}
|
||||
|
||||
echo "<div class='row'>";
|
||||
|
||||
// Group by server
|
||||
$servers = [];
|
||||
foreach ($servers_data as $data) {
|
||||
$server_id = $data['remote_server_id'];
|
||||
if (!isset($servers[$server_id])) {
|
||||
$servers[$server_id] = [
|
||||
'info' => $data,
|
||||
'latest' => null
|
||||
];
|
||||
}
|
||||
if (!$servers[$server_id]['latest'] || $data['timestamp'] > $servers[$server_id]['latest']['timestamp']) {
|
||||
$servers[$server_id]['latest'] = $data;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($servers as $server_id => $server) {
|
||||
$data = $server['latest'];
|
||||
$server_name = $data['agent_name'] ?: $data['agent_ip'];
|
||||
|
||||
echo "<div class='col-md-4'>";
|
||||
echo "<div class='card'>";
|
||||
echo "<div class='card-header'><h5>{$server_name}</h5></div>";
|
||||
echo "<div class='card-body'>";
|
||||
|
||||
// CPU Usage
|
||||
$cpu_class = $data['cpu_usage'] >= 80 ? 'danger' : ($data['cpu_usage'] >= 60 ? 'warning' : 'success');
|
||||
echo "<p><strong>CPU:</strong> <span class='badge badge-{$cpu_class}'>" . number_format($data['cpu_usage'], 1) . "%</span></p>";
|
||||
|
||||
// Memory Usage
|
||||
$mem_class = $data['memory_usage'] >= 80 ? 'danger' : ($data['memory_usage'] >= 60 ? 'warning' : 'success');
|
||||
echo "<p><strong>Memory:</strong> <span class='badge badge-{$mem_class}'>" . number_format($data['memory_usage'], 1) . "%</span>";
|
||||
echo " (" . number_format($data['memory_used_mb']/1024, 1) . "GB / " . number_format($data['memory_total_mb']/1024, 1) . "GB)</p>";
|
||||
|
||||
// Disk Usage
|
||||
$disk_class = $data['disk_usage'] >= 80 ? 'danger' : ($data['disk_usage'] >= 60 ? 'warning' : 'success');
|
||||
echo "<p><strong>Disk:</strong> <span class='badge badge-{$disk_class}'>" . number_format($data['disk_usage'], 1) . "%</span>";
|
||||
echo " (" . number_format($data['disk_used_mb']/1024, 1) . "GB / " . number_format($data['disk_total_mb']/1024, 1) . "GB)</p>";
|
||||
|
||||
echo "<p><small>Last Updated: " . date('Y-m-d H:i:s', strtotime($data['timestamp'])) . "</small></p>";
|
||||
|
||||
echo "</div></div></div>";
|
||||
}
|
||||
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='mt-4'>";
|
||||
echo "<a href='?m=resource_monitor&type=history' class='btn btn-info'>View History</a> ";
|
||||
echo "<a href='?m=resource_monitor&type=alerts' class='btn btn-warning'>Manage Alerts</a> ";
|
||||
echo "<a href='?m=resource_monitor&type=configure' class='btn btn-secondary'>Configuration</a>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Show configuration page
|
||||
*/
|
||||
function show_configuration()
|
||||
{
|
||||
echo "<h2>Resource Monitoring Configuration</h2>";
|
||||
|
||||
echo "<div class='alert alert-info'>";
|
||||
echo "<h5>Setup Instructions:</h5>";
|
||||
echo "<ol>";
|
||||
echo "<li>Add this to your server's crontab to collect data every 5 minutes:<br>";
|
||||
echo "<code>*/5 * * * * curl -s " . $_SERVER['HTTP_HOST'] . dirname($_SERVER['REQUEST_URI']) . "?m=resource_monitor&type=api_collect</code></li>";
|
||||
echo "<li>Configure Discord webhook URLs in the alerts section</li>";
|
||||
echo "<li>Set up alert thresholds for your servers and game instances</li>";
|
||||
echo "</ol>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<h4>Monitoring Status</h4>";
|
||||
|
||||
// Check if we have recent data
|
||||
global $db;
|
||||
$recent_data = $db->query("SELECT COUNT(*) as count FROM ogp_resource_monitoring WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 10 MINUTE)")->fetch();
|
||||
|
||||
if ($recent_data['count'] > 0) {
|
||||
echo "<div class='alert alert-success'>✅ Monitoring is active - " . $recent_data['count'] . " records in the last 10 minutes</div>";
|
||||
} else {
|
||||
echo "<div class='alert alert-warning'>⚠️ No recent monitoring data found. Please set up the cron job.</div>";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show resource history with charts
|
||||
*/
|
||||
function show_history()
|
||||
{
|
||||
global $db;
|
||||
|
||||
echo "<h2>Resource History</h2>";
|
||||
|
||||
// Simple table view for now - could be enhanced with charts later
|
||||
$query = "SELECT r.*, s.agent_name, s.agent_ip, h.home_name
|
||||
FROM ogp_resource_monitoring r
|
||||
LEFT JOIN ogp_remote_servers s ON r.remote_server_id = s.remote_server_id
|
||||
LEFT JOIN ogp_server_homes h ON r.home_id = h.home_id
|
||||
WHERE r.timestamp >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
|
||||
ORDER BY r.timestamp DESC
|
||||
LIMIT 100";
|
||||
|
||||
$result = $db->query($query);
|
||||
$history_data = $result->fetchAll();
|
||||
|
||||
echo "<table class='table table-striped'>";
|
||||
echo "<thead><tr><th>Time</th><th>Server</th><th>Target</th><th>CPU %</th><th>Memory %</th><th>Disk %</th></tr></thead>";
|
||||
echo "<tbody>";
|
||||
|
||||
foreach ($history_data as $data) {
|
||||
$server_name = $data['agent_name'] ?: $data['agent_ip'];
|
||||
$target = $data['home_id'] ? "Game Server ({$data['home_name']})" : "System";
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>" . date('m-d H:i', strtotime($data['timestamp'])) . "</td>";
|
||||
echo "<td>{$server_name}</td>";
|
||||
echo "<td>{$target}</td>";
|
||||
echo "<td>" . ($data['cpu_usage'] ? number_format($data['cpu_usage'], 1) . "%" : "-") . "</td>";
|
||||
echo "<td>" . ($data['memory_usage'] ? number_format($data['memory_usage'], 1) . "%" : "-") . "</td>";
|
||||
echo "<td>" . ($data['disk_usage'] ? number_format($data['disk_usage'], 1) . "%" : "-") . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage alerts configuration
|
||||
*/
|
||||
function manage_alerts()
|
||||
{
|
||||
global $db;
|
||||
|
||||
if (isset($_POST['add_alert'])) {
|
||||
add_alert();
|
||||
}
|
||||
|
||||
if (isset($_POST['update_discord_settings'])) {
|
||||
update_discord_settings();
|
||||
}
|
||||
|
||||
echo "<h2>Alert Management</h2>";
|
||||
|
||||
// Discord settings form
|
||||
echo "<h4>Discord Settings</h4>";
|
||||
echo "<form method='post'>";
|
||||
|
||||
$discord_settings = get_discord_settings();
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label>Default Webhook URL:</label>";
|
||||
echo "<input type='url' name='webhook_url' class='form-control' value='" . htmlspecialchars($discord_settings['default_webhook_url']) . "' placeholder='https://discord.com/api/webhooks/...'>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='form-group'>";
|
||||
echo "<label>Bot Username:</label>";
|
||||
echo "<input type='text' name='bot_username' class='form-control' value='" . htmlspecialchars($discord_settings['bot_username']) . "'>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<button type='submit' name='update_discord_settings' class='btn btn-primary'>Update Discord Settings</button>";
|
||||
echo "</form>";
|
||||
|
||||
echo "<hr>";
|
||||
|
||||
// Show existing alerts
|
||||
echo "<h4>Current Alerts</h4>";
|
||||
$alerts = $db->query("SELECT a.*, s.agent_name, s.agent_ip FROM ogp_resource_alerts a LEFT JOIN ogp_remote_servers s ON a.remote_server_id = s.remote_server_id ORDER BY a.created_at DESC")->fetchAll();
|
||||
|
||||
if (empty($alerts)) {
|
||||
echo "<p>No alerts configured yet.</p>";
|
||||
} else {
|
||||
echo "<table class='table'>";
|
||||
echo "<thead><tr><th>Server</th><th>Type</th><th>Threshold</th><th>Duration</th><th>Active</th><th>Last Triggered</th></tr></thead>";
|
||||
echo "<tbody>";
|
||||
|
||||
foreach ($alerts as $alert) {
|
||||
$server_name = $alert['agent_name'] ?: $alert['agent_ip'];
|
||||
$target = $alert['home_id'] ? "Game Server (ID: {$alert['home_id']})" : "System";
|
||||
|
||||
echo "<tr>";
|
||||
echo "<td>{$server_name}<br><small>{$target}</small></td>";
|
||||
echo "<td>" . strtoupper($alert['alert_type']) . "</td>";
|
||||
echo "<td>{$alert['threshold_percentage']}%</td>";
|
||||
echo "<td>{$alert['duration_minutes']} min</td>";
|
||||
echo "<td>" . ($alert['is_active'] ? "✅" : "❌") . "</td>";
|
||||
echo "<td>" . ($alert['last_triggered'] ? date('Y-m-d H:i', strtotime($alert['last_triggered'])) : "Never") . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
echo "</tbody></table>";
|
||||
}
|
||||
|
||||
echo "<hr>";
|
||||
|
||||
// Add new alert form
|
||||
echo "<h4>Add New Alert</h4>";
|
||||
echo "<form method='post'>";
|
||||
|
||||
echo "<div class='row'>";
|
||||
echo "<div class='col-md-4'>";
|
||||
echo "<label>Server:</label>";
|
||||
echo "<select name='remote_server_id' class='form-control' required>";
|
||||
$servers = $db->query("SELECT remote_server_id, agent_name, agent_ip FROM ogp_remote_servers ORDER BY agent_name, agent_ip")->fetchAll();
|
||||
foreach ($servers as $server) {
|
||||
$name = $server['agent_name'] ?: $server['agent_ip'];
|
||||
echo "<option value='{$server['remote_server_id']}'>{$name}</option>";
|
||||
}
|
||||
echo "</select>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='col-md-4'>";
|
||||
echo "<label>Alert Type:</label>";
|
||||
echo "<select name='alert_type' class='form-control' required>";
|
||||
echo "<option value='cpu'>CPU Usage</option>";
|
||||
echo "<option value='memory'>Memory Usage</option>";
|
||||
echo "<option value='disk'>Disk Usage</option>";
|
||||
echo "</select>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='col-md-4'>";
|
||||
echo "<label>Threshold (%):</label>";
|
||||
echo "<input type='number' name='threshold' class='form-control' value='80' min='1' max='100' required>";
|
||||
echo "</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='row mt-2'>";
|
||||
echo "<div class='col-md-4'>";
|
||||
echo "<label>Duration (minutes):</label>";
|
||||
echo "<input type='number' name='duration' class='form-control' value='30' min='5' required>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='col-md-8'>";
|
||||
echo "<label>Webhook URL (optional - uses default if empty):</label>";
|
||||
echo "<input type='url' name='webhook_url' class='form-control' placeholder='https://discord.com/api/webhooks/...'>";
|
||||
echo "</div>";
|
||||
echo "</div>";
|
||||
|
||||
echo "<div class='mt-3'>";
|
||||
echo "<button type='submit' name='add_alert' class='btn btn-success'>Add Alert</button>";
|
||||
echo "</div>";
|
||||
|
||||
echo "</form>";
|
||||
}
|
||||
|
||||
function get_discord_settings()
|
||||
{
|
||||
global $db;
|
||||
|
||||
$settings = [];
|
||||
$result = $db->query("SELECT setting_name, setting_value FROM ogp_discord_settings");
|
||||
|
||||
while ($row = $result->fetch()) {
|
||||
$settings[$row['setting_name']] = $row['setting_value'];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
function update_discord_settings()
|
||||
{
|
||||
global $db;
|
||||
|
||||
$settings = [
|
||||
'default_webhook_url' => $_POST['webhook_url'],
|
||||
'bot_username' => $_POST['bot_username']
|
||||
];
|
||||
|
||||
foreach ($settings as $name => $value) {
|
||||
$stmt = $db->prepare("UPDATE ogp_discord_settings SET setting_value = ? WHERE setting_name = ?");
|
||||
$stmt->execute([$value, $name]);
|
||||
}
|
||||
|
||||
echo "<div class='alert alert-success'>Discord settings updated successfully!</div>";
|
||||
}
|
||||
|
||||
function add_alert()
|
||||
{
|
||||
global $db;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO ogp_resource_alerts (remote_server_id, alert_type, threshold_percentage, duration_minutes, discord_webhook_url) VALUES (?, ?, ?, ?, ?)");
|
||||
|
||||
$webhook_url = !empty($_POST['webhook_url']) ? $_POST['webhook_url'] : null;
|
||||
|
||||
$stmt->execute([
|
||||
$_POST['remote_server_id'],
|
||||
$_POST['alert_type'],
|
||||
$_POST['threshold'],
|
||||
$_POST['duration'],
|
||||
$webhook_url
|
||||
]);
|
||||
|
||||
echo "<div class='alert alert-success'>Alert added successfully!</div>";
|
||||
}
|
||||
|
||||
?>
|
||||
45
scripts/resource_collector.php
Executable file
45
scripts/resource_collector.php
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/php
|
||||
<?php
|
||||
/*
|
||||
* Resource Collection Script for OGP Monitoring
|
||||
* This script should be run every 5 minutes via cron job
|
||||
*
|
||||
* Cron example:
|
||||
* 0,5,10,15,20,25,30,35,40,45,50,55 * * * * /usr/bin/php /path/to/GSP/scripts/resource_collector.php
|
||||
*/
|
||||
|
||||
// Set up basic environment
|
||||
chdir(dirname(__DIR__));
|
||||
require_once('includes/config.inc.php');
|
||||
require_once('includes/functions.php');
|
||||
require_once('includes/database.php');
|
||||
|
||||
// Connect to database
|
||||
$db = createDatabaseConnection($db_type, $db_host, $db_user, $db_pass, $db_name, $table_prefix);
|
||||
|
||||
if (!$db instanceof OGPDatabase) {
|
||||
error_log("Resource collector: Failed to connect to database");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Include resource monitoring functions
|
||||
require_once('modules/resource_monitor/resource_functions.php');
|
||||
|
||||
echo date('Y-m-d H:i:s') . " - Starting resource collection\n";
|
||||
|
||||
try {
|
||||
// Collect resources from all agents
|
||||
collect_all_resources();
|
||||
|
||||
// Clean up old data
|
||||
cleanup_old_data();
|
||||
|
||||
echo date('Y-m-d H:i:s') . " - Resource collection completed successfully\n";
|
||||
} catch (Exception $e) {
|
||||
error_log("Resource collector error: " . $e->getMessage());
|
||||
echo date('Y-m-d H:i:s') . " - Resource collection failed: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
exit(0);
|
||||
?>
|
||||
Loading…
Add table
Add a link
Reference in a new issue