acquireCommandLock('storage:monitor', $lockSeconds, (bool) $this->option('force')); if ($lock === false) { $this->warn('storage:monitor is already running on another worker.'); return self::SUCCESS; } try { $targets = MediaStorageTarget::active()->get(); if ($targets->isEmpty()) { $this->info('No media storage targets available.'); return self::SUCCESS; } $assetStats = $this->buildAssetStatistics(); $thresholds = $this->capacityThresholds(); $alerts = []; $snapshotTargets = []; foreach ($targets as $target) { $capacity = $this->storageHealth->getCapacity($target); $assets = $assetStats[$target->id] ?? [ 'total' => 0, 'bytes' => 0, 'by_status' => [], ]; $severity = $this->determineCapacitySeverity($capacity, $thresholds); if ($severity !== 'ok') { $alerts[] = [ 'target' => $target->key, 'type' => 'capacity', 'severity' => $severity, 'percentage' => $capacity['percentage'] ?? null, 'status' => $capacity['status'] ?? null, ]; } $failedCount = $assets['by_status']['failed']['count'] ?? 0; if ($failedCount > 0) { $alerts[] = [ 'target' => $target->key, 'type' => 'failed_assets', 'severity' => 'warning', 'failed' => $failedCount, ]; } $snapshotTargets[] = [ 'id' => $target->id, 'key' => $target->key, 'name' => $target->name, 'is_hot' => (bool) $target->is_hot, 'capacity' => $capacity, 'assets' => $assets, ]; } $snapshot = [ 'generated_at' => now()->toIso8601String(), 'targets' => $snapshotTargets, 'alerts' => $alerts, ]; $ttlMinutes = max(1, (int) config('storage-monitor.monitor.cache_minutes', 15)); Cache::put('storage:monitor:last', $snapshot, now()->addMinutes($ttlMinutes)); Log::channel('storage-jobs')->info('Storage monitor snapshot generated', [ 'targets' => count($snapshotTargets), 'alerts' => count($alerts), ]); $this->info(sprintf( 'Storage monitor finished: %d targets, %d alerts.', count($snapshotTargets), count($alerts) )); return self::SUCCESS; } finally { if ($lock instanceof Lock) { $lock->release(); } } } /** * @return array */ private function buildAssetStatistics(): array { return EventMediaAsset::query() ->selectRaw('media_storage_target_id, status, COUNT(*) as total_count, COALESCE(SUM(size_bytes), 0) as total_bytes') ->groupBy('media_storage_target_id', 'status') ->get() ->groupBy('media_storage_target_id') ->map(function ($rows) { $byStatus = []; $totalCount = 0; $totalBytes = 0; foreach ($rows as $row) { $count = (int) ($row->total_count ?? 0); $bytes = (int) ($row->total_bytes ?? 0); $totalCount += $count; $totalBytes += $bytes; $byStatus[$row->status] = [ 'count' => $count, 'bytes' => $bytes, ]; } ksort($byStatus); return [ 'total' => $totalCount, 'bytes' => $totalBytes, 'by_status' => $byStatus, ]; }) ->all(); } private function capacityThresholds(): array { $warning = (int) config('storage-monitor.capacity_thresholds.warning', 75); $critical = (int) config('storage-monitor.capacity_thresholds.critical', 90); if ($warning > $critical) { [$warning, $critical] = [$critical, $warning]; } return [ 'warning' => $warning, 'critical' => $critical, ]; } private function determineCapacitySeverity(array $capacity, array $thresholds): string { $status = $capacity['status'] ?? 'ok'; if ($status !== 'ok') { return $status; } $percentage = $capacity['percentage'] ?? null; if ($percentage === null) { return 'unknown'; } if ($percentage >= ($thresholds['critical'] ?? 95)) { return 'critical'; } if ($percentage >= ($thresholds['warning'] ?? 75)) { return 'warning'; } return 'ok'; } }