Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/Progressable.php
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,45 @@ public function getEstimatedTimeRemaining(): ?int {
return (int) round($remainingProgress / $rate);
}

/**
* Get the estimated time remaining in seconds for the overall progress.
*
* @throws UniqueNameNotSetException
*/
public function getOverallEstimatedTimeRemaining(): ?int {
$overallProgress = $this->getOverallProgress(0);

if ($overallProgress >= 100) {
return 0;
}

$progressData = $this->getOverallProgressData();
$earliestStartTime = null;

foreach ($progressData as $localData) {
if (isset($localData['start_time'])) {
if ($earliestStartTime === null || $localData['start_time'] < $earliestStartTime) {
$earliestStartTime = $localData['start_time'];
}
}
}

if ($earliestStartTime === null || $overallProgress <= 0) {
return null;
}

$elapsed = Carbon::now()->timestamp - $earliestStartTime;

if ($elapsed <= 0) {
return null;
}

$rate = $overallProgress / $elapsed; // progress per second
$remainingProgress = 100 - $overallProgress;

return (int) round($remainingProgress / $rate);
}

/**
* Remove this instance from the overall progress calculation.
*
Expand Down Expand Up @@ -659,6 +698,7 @@ public function toArray(): array {
'is_complete' => $this->isComplete(),
'is_overall_complete' => $hasUniqueName ? $this->isOverallComplete() : null,
'estimated_time_remaining' => $hasUniqueName ? $this->getEstimatedTimeRemaining() : null,
'overall_estimated_time_remaining' => $hasUniqueName ? $this->getOverallEstimatedTimeRemaining() : null,
'message' => $this->getStatusMessage(),
'metadata' => $this->getMetadata(),
'total_steps' => $this->getTotalSteps(),
Expand Down
65 changes: 65 additions & 0 deletions tests/EtaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,69 @@ public function test_start_time_persistence(): void {

$this->assertEquals(40, $obj2->getEstimatedTimeRemaining());
}

public function test_overall_eta_is_null_initially(): void {
$this->setOverallUniqueName('test_overall_eta_init_'.$this->testId);
if (method_exists($this, 'getOverallEstimatedTimeRemaining')) {
$this->assertNull($this->getOverallEstimatedTimeRemaining());
} else {
$this->markTestSkipped('getOverallEstimatedTimeRemaining not implemented yet');
}
}

public function test_overall_eta_calculation(): void {
if (! method_exists($this, 'getOverallEstimatedTimeRemaining')) {
$this->markTestSkipped('getOverallEstimatedTimeRemaining not implemented yet');
}

Carbon::setTestNow(Carbon::now());
$uniqueName = 'test_overall_eta_calc_'.$this->testId;

$this->setOverallUniqueName($uniqueName);
$this->setLocalProgress(0); // Start time set at T0

// Create new instance simulating another process or request
$obj2 = new class {
use Progressable;
};
$obj2->setOverallUniqueName($uniqueName);

// Advance time by 10 seconds
Carbon::setTestNow(Carbon::now()->addSeconds(10));

// obj1 progress to 10%
$this->setLocalProgress(10);

// obj2 progress to 10%
$obj2->setLocalProgress(10);

// Overall progress = 10%. Elapsed = 10s. Rate = 1% / s.
// Remaining 90%. ETA = 90s.
$this->assertEquals(90, $this->getOverallEstimatedTimeRemaining());
$this->assertEquals(90, $obj2->getOverallEstimatedTimeRemaining());

// Advance time by another 10 seconds (total 20s)
Carbon::setTestNow(Carbon::now()->addSeconds(10));

// obj1 progress to 50%
$this->setLocalProgress(50);

// obj2 progress to 50%
$obj2->setLocalProgress(50);

// Overall progress = 50%. Elapsed = 20s. Rate = 2.5% / s.
// Remaining 50%. ETA = 50 / 2.5 = 20s.
$this->assertEquals(20, $this->getOverallEstimatedTimeRemaining());
$this->assertEquals(20, $obj2->getOverallEstimatedTimeRemaining());
}

public function test_overall_eta_is_zero_when_complete(): void {
if (! method_exists($this, 'getOverallEstimatedTimeRemaining')) {
$this->markTestSkipped('getOverallEstimatedTimeRemaining not implemented yet');
}

$this->setOverallUniqueName('test_overall_eta_complete_'.$this->testId);
$this->setLocalProgress(100);
$this->assertEquals(0, $this->getOverallEstimatedTimeRemaining());
}
}
2 changes: 2 additions & 0 deletions tests/ProgressableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ public function test_to_array(): void {
$this->assertFalse($array['is_complete']);
$this->assertFalse($array['is_overall_complete']);
$this->assertNull($array['estimated_time_remaining']);
$this->assertNull($array['overall_estimated_time_remaining']);
$this->assertEquals('Halfway there', $array['message']);
$this->assertEquals(['foo' => 'bar'], $array['metadata']);
$this->assertEquals(10, $array['total_steps']);
Expand All @@ -587,6 +588,7 @@ public function test_to_array_without_unique_name(): void {
'is_complete' => false,
'is_overall_complete' => null,
'estimated_time_remaining' => null,
'overall_estimated_time_remaining' => null,
'message' => 'Halfway there',
'metadata' => ['foo' => 'bar'],
'total_steps' => 10,
Expand Down
Loading