From d7c7ca093bcd130d7e76346e04defaecb6fa1d0a Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Wed, 4 Nov 2026 04:08:20 +0100 Subject: [PATCH] target:add: estimate the time left only once enough of the index is in The first index file also carries the time to connect, which made an S3 location say "about 642 min left" for something done within a minute. --- lib/Command/TargetAdd.php | 25 ++++++++++++---- tests/Unit/Command/TargetAddEstimateTest.php | 30 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 tests/Unit/Command/TargetAddEstimateTest.php diff --git a/lib/Command/TargetAdd.php b/lib/Command/TargetAdd.php index b56d250..0d24c1b 100644 --- a/lib/Command/TargetAdd.php +++ b/lib/Command/TargetAdd.php @@ -88,19 +88,21 @@ HELP); * @return callable(int, int): void */ private static function progress(OutputInterface $output): callable { - $start = microtime(true); $shown = 0.0; + $first = null; // [time, files done] when the first files were in $inPlace = $output->isDecorated(); - return static function (int $done, int $total) use ($output, $start, &$shown, $inPlace): void { + return static function (int $done, int $total) use ($output, &$shown, &$first, $inPlace): void { $now = microtime(true); + if ($first === null && $done > 0) { + $first = [$now, $done]; + } if ($done < $total && $now - $shown < ($inPlace ? 1 : 30)) { return; } $shown = $now; $line = sprintf('Reading the index: %s/%s files (%d%%)', number_format($done), number_format($total), intdiv(100 * $done, max(1, $total))); - $elapsed = $now - $start; - if ($done < $total && $done > 0 && $elapsed >= 5) { - $left = (int)ceil($elapsed / (float)$done * (float)($total - $done) / 60.0); + $left = $first === null ? null : self::minutesLeft($now - $first[0], $done - $first[1], $total - $done, $total); + if ($left !== null) { $line .= $left <= 1 ? ', less than a minute left' : sprintf(', about %d min left', $left); } if ($inPlace) { @@ -110,4 +112,17 @@ HELP); } }; } + + /** + * Minutes left for reading the index, from the rate since the first files were in: the first + * file also carries the time to connect, which made an S3 location with 4,568 index files + * say "about 642 min left" for something done within a minute. No estimate until 5% of the + * files, or 50 files, came in after that, and at least 5 seconds passed. + */ + public static function minutesLeft(float $elapsed, int $done, int $left, int $total): ?int { + if ($left <= 0 || $done <= 0 || $elapsed < 5.0 || ($done < 50 && $done * 20 < $total)) { + return null; + } + return (int)ceil($elapsed / (float)$done * (float)$left / 60.0); + } } diff --git a/tests/Unit/Command/TargetAddEstimateTest.php b/tests/Unit/Command/TargetAddEstimateTest.php new file mode 100644 index 0000000..8c63a4c --- /dev/null +++ b/tests/Unit/Command/TargetAddEstimateTest.php @@ -0,0 +1,30 @@ +assertNull(TargetAdd::minutesLeft(6.0, 10, 4558, 4568)); + } + + public function testEstimateOnceEnoughFilesCameIn(): void { + // 300 files in 10 s: 4,268 left at 30 a second is about 2.4 minutes + $this->assertSame(3, TargetAdd::minutesLeft(10.0, 300, 4268, 4568)); + } + + public function testFivePercentOfASmallIndexIsEnough(): void { + // 6 of 100 files in 6 s: 94 left at one a second is about 1.6 minutes + $this->assertSame(2, TargetAdd::minutesLeft(6.0, 6, 94, 100)); + } + + public function testNoEstimateInTheFirstSecondsOrWhenDone(): void { + $this->assertNull(TargetAdd::minutesLeft(2.0, 500, 4000, 4500)); + $this->assertNull(TargetAdd::minutesLeft(60.0, 4568, 0, 4568)); + } +}