> */ private $runs = []; /** * @var ?Timer */ private static $instance; public function __construct(bool $isArchivePhpTriggered, ClockInterface $clock, WriterInterface $writer) { $this->isArchivePhpTriggered = $isArchivePhpTriggered; $this->clock = $clock; $this->writer = $writer; } public static function getInstance(bool $isArchivePhpTriggered, ?ClockInterface $clock = null, ?WriterInterface $writer = null): self { if (self::$instance !== null) { return self::$instance; } if ($clock === null) { $clock = new Clock(); } if ($writer === null) { $writer = new DbWriter(); } self::$instance = new self($isArchivePhpTriggered, $clock, $writer); return self::$instance; } /** * @internal For tests only. */ public static function resetInstanceForTests(): void { self::$instance = null; } public function start(Context $context): void { if (false === $this->isApplicableForTiming($context)) { return; } $this->runs[$context->getKey()] = [ 'context' => $context, 'timeStarted' => $this->clock->microtime(), ]; } public function complete(Context $context, array $idArchives, bool $wasCached): void { if (false === $this->isApplicableForTiming($context)) { return; } if (true === $wasCached) { return; } if (empty($idArchives) || count($idArchives) !== 1) { return; } $idArchive = reset($idArchives); $key = $context->getKey(); if (!isset($this->runs[$key]['timeStarted'])) { return; } $finishedAt = $this->clock->microtime(); $totalTimeMs = ($finishedAt - $this->runs[$key]['timeStarted']); $this->runs[$key]['ts_started'] = date('Y-m-d H:i:s', (int) $this->runs[$key]['timeStarted']); $this->runs[$key]['totalTime'] = $totalTimeMs; $this->runs[$key]['timeFinished'] = $finishedAt; $this->runs[$key]['ts_finished'] = date('Y-m-d H:i:s', (int) $this->runs[$key]['timeFinished']); $exclusiveTimeMs = $this->calculateExclusiveTime($key); $this->runs[$key]['exclusiveTime'] = $exclusiveTimeMs; /** @var Context $storedContext */ $storedContext = $this->runs[$key]['context']; $archiveName = Rules::getDoneStringFlagFor( [$storedContext->idSite], $storedContext->segment, $storedContext->period->getLabel(), $storedContext->plugin ); $this->writer->write($storedContext, [ 'idarchive' => $idArchive, 'archive_name' => $archiveName, 'ts_started' => $this->runs[$key]['ts_started'], 'ts_finished' => $this->runs[$key]['ts_finished'], 'total_time' => (int) round($totalTimeMs * 1000), 'total_time_exclusive' => (int) round($exclusiveTimeMs * 1000), 'is_temporary' => $this->isTemporaryArchive($storedContext, (int) $this->runs[$key]['timeStarted']), ]); } private function isTemporaryArchive(Context $context, int $archiveStartTimestamp): int { $archiveStartInSiteTimezone = Date::factory($archiveStartTimestamp, $context->siteTimezone); $periodEndInSiteTimezone = $context->period->getDateTimeEnd()->setTimezone($context->siteTimezone); return (int) $archiveStartInSiteTimezone->isEarlier($periodEndInSiteTimezone); } private function isApplicableForTiming(Context $context): bool { if (false === $this->isArchivePhpTriggered) { return false; } $doneFlag = Rules::getDoneStringFlagFor( [$context->idSite], $context->segment, $context->period->getLabel(), $context->plugin ); if (strpos($doneFlag, '.') !== false) { return false; } return true; } private function calculateExclusiveTime(string $currentKey): float { if (empty($this->runs[$currentKey])) { return 0.0; } $current = $this->runs[$currentKey]; $totalTimeMs = $current['totalTime']; // If the key is last in the array then it's probably not a nested archive so this calculation doesn't matter if ($currentKey === array_key_last($this->runs)) { return $totalTimeMs; } $childTotalMs = 0.0; foreach ($this->runs as $otherKey => $run) { if ($otherKey === $currentKey) { continue; } if (empty($run['exclusiveTime'])) { continue; } $childFinished = $run['timeFinished'] ?? null; $childStarted = $run['timeStarted'] ?? null; if ($childFinished === null || $childStarted === null) { continue; } if ($childFinished <= $current['timeFinished'] && $childStarted >= $current['timeStarted']) { $childTotalMs += $run['exclusiveTime']; } } $exclusive = $totalTimeMs - $childTotalMs; if ($exclusive < 0) { return $totalTimeMs; } return $exclusive; } }