null; if ($profile->mutates() && ! $options->allowWrite) { return BenchmarkResult::skipped($profile, '데이터를 변경하는 배치입니다. --allow-write 를 붙여 실행하세요.'); } $command = (string) $profile->option('command'); $arguments = (array) $profile->option('arguments', []); if (! array_key_exists($command, Artisan::all())) { return BenchmarkResult::skipped($profile, "등록되지 않은 커맨드: {$command} (해당 확장이 설치되어 있는지 확인)"); } $notify("배치 실행: {$command}"); // 피크 메모리는 프로세스 단위 누적값이라 감소하지 않는다. 실행 전 값을 함께 // 기록해야 "이 배치가 피크를 밀어올렸는지"를 판정할 수 있다. $peakBefore = memory_get_peak_usage(true); $usageBefore = memory_get_usage(true); $buffer = new BufferedOutput; $start = microtime(true); try { $collected = $this->collector->collect( static fn () => Artisan::call($command, $arguments, $buffer) ); $exitCode = (int) $collected['value']; $queries = $collected['queries']; } catch (\Throwable $e) { return BenchmarkResult::skipped($profile, '계측 실패: '.$e->getMessage()); } $elapsedMs = (microtime(true) - $start) * 1000; $peakAfter = memory_get_peak_usage(true); $usageAfter = memory_get_usage(true); $summary = $this->collector->summarize($queries); $notes = [ sprintf('실행 전 피크 %s → 실행 후 피크 %s', $this->formatBytes($peakBefore), $this->formatBytes($peakAfter)), ]; if ($peakAfter <= $peakBefore) { $notes[] = '이 배치가 프로세스 피크를 밀어올리지 않았습니다 (실행 전 피크가 이미 더 높음).'; } if ($exitCode !== 0) { $notes[] = "커맨드가 실패 종료했습니다 (exit={$exitCode}) — 시간·메모리는 실패 지점까지의 값입니다."; } $output = trim($buffer->fetch()); if ($output !== '') { $notes[] = '커맨드 출력: '.$output; } return new BenchmarkResult( profile: $profile, headers: ['커맨드', '종료코드', '소요(ms)', '피크 메모리', '메모리 증가', '쿼리(건)'], rows: [[ $command, (string) $exitCode, number_format($elapsedMs, 1), $this->formatBytes($peakAfter), $this->formatBytes(max(0, $usageAfter - $usageBefore)), number_format($summary['count']), ]], metrics: [ 'command' => $command, 'arguments' => $arguments, 'exit_code' => $exitCode, 'elapsed_ms' => round($elapsedMs, 2), 'peak_memory_before_bytes' => $peakBefore, 'peak_memory_after_bytes' => $peakAfter, 'memory_delta_bytes' => $usageAfter - $usageBefore, 'query_count' => $summary['count'], 'db_ms' => $summary['db_ms'], ], notes: $notes, ); } /** * 바이트를 사람이 읽는 단위로 바꿉니다. * * @param int $bytes 바이트 * @return string 포맷된 문자열 */ private function formatBytes(int $bytes): string { if ($bytes >= 1024 ** 3) { return number_format($bytes / 1024 ** 3, 2).' GB'; } if ($bytes >= 1024 ** 2) { return number_format($bytes / 1024 ** 2, 1).' MB'; } return number_format($bytes / 1024, 1).' KB'; } }