Files
jabali-panel/app/Console/Commands/CollectUserUsage.php
2026-01-28 00:50:06 +02:00

174 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Mailbox;
use App\Models\User;
use App\Models\UserResourceUsage;
use App\Models\UserSetting;
use App\Services\Agent\AgentClient;
use Exception;
use Illuminate\Console\Command;
class CollectUserUsage extends Command
{
protected $signature = 'jabali:collect-user-usage {--user=} {--retain=90}';
protected $description = 'Collect per-user resource usage snapshots';
public function handle(): int
{
$users = User::query()
->where('is_admin', false)
->where('is_active', true);
$userFilter = $this->option('user');
if ($userFilter) {
$users->where(function ($query) use ($userFilter) {
$query->where('id', $userFilter)
->orWhere('username', $userFilter);
});
}
$users = $users->get();
if ($users->isEmpty()) {
$this->info('No users found for usage collection.');
return 0;
}
$agent = new AgentClient;
$capturedAt = now();
foreach ($users as $user) {
$diskBytes = $user->getDiskUsageBytes();
$mailBytes = (int) Mailbox::where('user_id', $user->id)->sum('quota_used_bytes');
$dbBytes = $this->getDatabaseUsage($agent, $user);
$bandwidthTotal = $this->getBandwidthTotal($agent, $user);
$resourceStats = $this->getUserResourceStats($agent, $user);
$cpuPercent = (int) round($resourceStats['cpu_percent'] ?? 0);
$cpuUsageTotal = (int) ($resourceStats['cpu_usage_usec_total'] ?? 0);
$memoryBytes = (int) ($resourceStats['memory_bytes'] ?? 0);
$diskIoTotal = (int) ($resourceStats['disk_io_total_bytes'] ?? 0);
$lastBandwidthTotal = (int) UserSetting::getForUser($user->id, 'bandwidth_total_bytes', 0);
$lastDiskIoTotal = (int) UserSetting::getForUser($user->id, 'disk_io_total_bytes', 0);
$lastCpuTotal = (int) UserSetting::getForUser($user->id, 'cpu_usage_usec_total', 0);
$lastCpuAt = (int) UserSetting::getForUser($user->id, 'cpu_usage_captured_at', 0);
$bandwidthDelta = $bandwidthTotal >= $lastBandwidthTotal
? $bandwidthTotal - $lastBandwidthTotal
: $bandwidthTotal;
$diskIoDelta = $diskIoTotal >= $lastDiskIoTotal
? $diskIoTotal - $lastDiskIoTotal
: $diskIoTotal;
if ($cpuUsageTotal > 0 && $lastCpuAt > 0) {
$elapsed = $capturedAt->timestamp - $lastCpuAt;
if ($elapsed > 0) {
$delta = $cpuUsageTotal >= $lastCpuTotal ? $cpuUsageTotal - $lastCpuTotal : $cpuUsageTotal;
$cpuPercent = (int) round(($delta / ($elapsed * 1_000_000)) * 100);
}
}
$this->storeMetric($user->id, 'disk_bytes', $diskBytes, $capturedAt);
$this->storeMetric($user->id, 'mail_bytes', $mailBytes, $capturedAt);
$this->storeMetric($user->id, 'database_bytes', $dbBytes, $capturedAt);
$this->storeMetric($user->id, 'bandwidth_bytes', $bandwidthDelta, $capturedAt);
$this->storeMetric($user->id, 'cpu_percent', $cpuPercent, $capturedAt);
$this->storeMetric($user->id, 'memory_bytes', $memoryBytes, $capturedAt);
$this->storeMetric($user->id, 'disk_io_bytes', $diskIoDelta, $capturedAt);
UserSetting::setForUser($user->id, 'bandwidth_total_bytes', $bandwidthTotal);
UserSetting::setForUser($user->id, 'disk_io_total_bytes', $diskIoTotal);
UserSetting::setForUser($user->id, 'cpu_usage_usec_total', $cpuUsageTotal);
UserSetting::setForUser($user->id, 'cpu_usage_captured_at', $capturedAt->timestamp);
$this->line("Collected usage for {$user->username}");
}
$retainDays = (int) $this->option('retain');
if ($retainDays > 0) {
UserResourceUsage::where('captured_at', '<', now()->subDays($retainDays))->delete();
}
return 0;
}
protected function getDatabaseUsage(AgentClient $agent, User $user): int
{
try {
$result = $agent->mysqlListDatabases($user->username);
$databases = $result['databases'] ?? [];
$total = 0;
foreach ($databases as $database) {
$total += (int) ($database['size_bytes'] ?? 0);
}
return $total;
} catch (Exception $e) {
$this->warn("Failed to fetch database usage for {$user->username}: {$e->getMessage()}");
}
return 0;
}
protected function getBandwidthTotal(AgentClient $agent, User $user): int
{
try {
$result = $agent->send('usage.bandwidth_total', [
'username' => $user->username,
]);
if ($result['success'] ?? false) {
return (int) ($result['total_bytes'] ?? 0);
}
} catch (Exception $e) {
$this->warn("Failed to fetch bandwidth usage for {$user->username}: {$e->getMessage()}");
}
return 0;
}
/**
* @return array{cpu_percent: float, cpu_usage_usec_total: int, memory_bytes: int, disk_io_total_bytes: int}
*/
protected function getUserResourceStats(AgentClient $agent, User $user): array
{
try {
$result = $agent->send('usage.user_resources', [
'username' => $user->username,
]);
if ($result['success'] ?? false) {
return [
'cpu_percent' => (float) ($result['cpu_percent'] ?? 0),
'cpu_usage_usec_total' => (int) ($result['cpu_usage_usec_total'] ?? 0),
'memory_bytes' => (int) ($result['memory_bytes'] ?? 0),
'disk_io_total_bytes' => (int) ($result['disk_io_total_bytes'] ?? 0),
];
}
} catch (Exception $e) {
$this->warn("Failed to fetch CPU/memory/disk IO for {$user->username}: {$e->getMessage()}");
}
return [
'cpu_percent' => 0.0,
'cpu_usage_usec_total' => 0,
'memory_bytes' => 0,
'disk_io_total_bytes' => 0,
];
}
protected function storeMetric(int $userId, string $metric, int $value, $capturedAt): void
{
UserResourceUsage::create([
'user_id' => $userId,
'metric' => $metric,
'value' => max(0, $value),
'captured_at' => $capturedAt,
]);
}
}