Я использую 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 секунд.
Как я могу это исправить? Можно ли как-то указать начальную позицию прогресс-бара до запуская его, чтобы ETA правильно рассчитывалось?
Это не поддерживается помощником ProgressBar
.
Расчетное время рассчитывается методом ProgressBar#getEstimated()
, который очень прост:
public function getEstimated(): float
{
if (!$this->step) {
return 0;
}
return round((time() - $this->startTime) / $this->step * $this->max);
}
При этом учитывается только количество времени, прошедшее с момента запуска индикатора выполнения (которое устанавливается при вызове ProgressBar#start()
или в конструкторе в противном случае), и общее количество «шагов», пройденных на данный момент.
Нет различия между «настоящими» шагами и «фальшивыми» шагами. Даже если бы кто-то изменил ProgresBar#step
перед вызовом start()
(например, в конструкторе), результат был бы таким же, поскольку вычисление расчетного времени будет работать точно так же.
Класс помечен final
, что, на мой взгляд, неудачно для вспомогательного класса, поэтому вы не можете просто расширить его и добавить свою логику (например, с дополнительным свойством int $resumedAt
, которое можно было бы использовать при расчете предполагаемого оставшегося времени).
Если вам это очень-очень нужно, я бы просто сделал копию класса в вашем проекте и добавил туда необходимую логику.
В качестве простого доказательства концепции я скопировал 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();
Я играл с этим, и ему нужно дополнительное свойство, такое как $resumedAt
, как вы упомянули. ИМО определенно нуждается в добавлении этой поддержки.
Если я смогу найти время позже на этой неделе, я отправлю PR в symfony.
Хороший. Почти то же самое, с чем я экспериментировал.
Спасибо за ваш обширный ответ. С нетерпением жду вашего пиара на Symfony! Не могли бы вы опубликовать ссылку здесь, если вы продолжите?
Наконец-то сегодня нашел время, @BenMorel. Посмотрим, полетит ли: github.com/symfony/symfony/pull/46242
Я могу подтвердить это. Возможно, лучше всего опубликовать проблему на Репозиторий Symfony.