在给定位置(重新)开始时如何正确计算 ProgressBar 中的 ETA?

How to properly calculate ETA in ProgressBar when (re)starting at a given position?

我正在为我的控制台命令使用 ProgressBar,并允许命令在处理过程中的某个时间点重新启动:

$itemCount = 1_000_000;
$startItem = 150_000;

$progressBar = new ProgressBar($output, $itemCount);
$progressBar->setFormat(
    $progressBar->getFormatDefinition(ProgressBar::FORMAT_DEBUG)
);

$progressBar->start();

if ($startItem !== 0) {
    // unfortunately taken into account in ETA calculation
    $progressBar->advance($startItem);
}

for ($i = $startItem; $i < $itemCount; $i++) {
    usleep(50_000);
    $progressBar->advance();
}

问题是,即使该命令每秒仅处理约 20 个项目,计算 ETA 时就好像 $startItem 个项目在处理开始时就已处理:

150038/1000000 [====>-----------------------]  15% 2 secs/13 secs 20.0 MiB

我预计预计到达时间约为 12 小时,而不是 13 秒。

我该如何解决这个问题?我能以某种方式在开始之前给出进度条的起始位置吗?计算正确吗?

ProgressBar 助手不支持此功能。

预估时间是通过ProgressBar#getEstimated()方法计算出来的,很简单:

public function getEstimated(): float
{
    if (!$this->step) {
         return 0;
    }

    return round((time() - $this->startTime) / $this->step * $this->max);
}

这只考虑了自进度条开始以来的时间量(这是在调用 ProgressBar#start() 时设置的,否则在构造函数中设置),以及它的“步数”总数到目前为止。

“真”步和“假”步之间没有区别。即使在调用 start() 之前修改 ProgresBar#step(例如在构造函数中),结果也是一样的,因为估计时间的计算方式完全相同。

class 被标记为 final,我认为这对于助手 class 来说是不幸的,因此您不能简单地扩展它并添加您的逻辑(例如,使用额外的 int $resumedAt 属性 可以在计算估计剩余时间时使用)。

如果您真的非常需要这个,我只需复制您项目中的 class 并在其中添加必要的逻辑。

作为一个简单的概念证明,我将 ProgressBar 复制到 App\ProgressBar,并添加了:

private int $resumedSteps = 0;

public function resume(int $max = null, int $step = 0): void
{

    $this->startTime = time();
    $this->resumedStep = $step;
    $this->step = $step;

    if (null !== $max) {
        $this->setMaxSteps($max);
    }

    $this->setProgress($step);

    $this->display();
}

public function getEstimated(): float
{
   if ($this->step === 0 || $this->step === $this->resumedStep) {
      return 0;
   }

   return round((time() - $this->startTime) / ($this->step - $this->resumedStep) * $this->max);
}

public function getRemaining(): float
{
   if ($this->step === 0 || $this->step === $this->resumedStep) {
     return 0;
   }

   return round((time() - $this->startTime) / ($this->step - $this->resumedStep) * ($this->max - $this->step));
}

人们可以简单地将其用作:

$itemCount = 1_000_000;

$progressBar = new App\ProgressBar($output, $itemCount);
$progressBar->setFormat(
    $progressBar->getFormatDefinition(ProgressBar::FORMAT_DEBUG)
);

$startItem > 0 ? $progressBar->resume($startItem) : $progressBar->start();