Как правильно рассчитать ETA в ProgressBar при (повторном) запуске с заданной позиции?

Я использую 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 правильно рассчитывалось?

Я могу подтвердить это. Возможно, лучше всего опубликовать проблему на Репозиторий Symfony.

Bossman 03.05.2022 13:54
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2
1
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не поддерживается помощником 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, как вы упомянули. ИМО определенно нуждается в добавлении этой поддержки.

Bossman 03.05.2022 14:32

Если я смогу найти время позже на этой неделе, я отправлю PR в symfony.

yivi 03.05.2022 14:57

Хороший. Почти то же самое, с чем я экспериментировал.

Bossman 03.05.2022 15:02

Спасибо за ваш обширный ответ. С нетерпением жду вашего пиара на Symfony! Не могли бы вы опубликовать ссылку здесь, если вы продолжите?

BenMorel 03.05.2022 17:32

Наконец-то сегодня нашел время, @BenMorel. Посмотрим, полетит ли: github.com/symfony/symfony/pull/46242

yivi 03.05.2022 18:25

Другие вопросы по теме