Индикатор выполнения

Дата обновления перевода 2024-06-24

Индикатор выполнения

При выполненнии более длительных команд, может быть полезно отображать информацию о выполнении, которая обновляется во время исполнения команды:

В выводе консоли отображается индикатор выполнения, продвигающийся к 100%, с указанием предполагаемого оставшегося времени, используемой памяти и специального сообщения, которое меняется, когда индикатор закрывает завершение.

Note

В качестве альтернативы можно использовать SymfonyStyle для отображения индикатора выполнения.

Чтобы отобразить детали выполнения, используйте ProgressBar, передайте ему общее количество единиц и продвиньте выполнение во время команды:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use Symfony\Component\Console\Helper\ProgressBar;

// создаёт новый индикатор выполнения (50 единиц)
$progressBar = new ProgressBar($output, 50);

// запускает и отображает индикатор выполнения
$progressBar->start();

$i = 0;
while ($i++ < 50) {
    // ... сделать какую-то работу

    // продвигает индикатор выполнения на 1 едииицу
    $progressBar->advance();

    // вы также можете продвинуть индикатор выполнения больше, чем на 1 единицу
    // $progressBar->advance(3);
}

// гарантирует, что индикатор выполнения достиг 100%
$progressBar->finish();

Tip

Вы также можете продвинуться по индикатору в другом направлении (т.е. сделать шаг назад), вызвав $progress->advance() с отрицательным значением. Например, если вы вызовете $progress->advance(-2), то это уменьшит индикатор выполнения на 2 шага.

Note

По умолчанию помощник индикатора выполнения использует вывод ошибок (stderr) в качестве в качестве вывода по умолчанию. Это поведение можно изменить, передав экземпляр StreamOutput в конструктор ProgressBar.

Вместо продвижения индикатора на количество шагов (с методом advance()), вы также можете установить текущий прогресс, вызвав метод setProgress().

Если вы возобновляете выполнение давних задач, полезно начать отображать индикатор выполнения с определённой точки. Используйте второй необязательный аргумент start(), чтобы установить начальную точку:

1
2
3
4
5
6
7
use Symfony\Component\Console\Helper\ProgressBar;

// создаёт новый индиактор выполнения (100 единиц)
$progressBar = new ProgressBar($output, 100);

// отображает индикатор выполнения, который начинается с 25 заполненных единиц
$progressBar->start(null, 25);

Tip

Если ваша платформа не поддерживает коды ANSI, обновления индикатора выполнения добавляются как новые строки. Чтобы избежать затопления вывода, используйте метод minSecondsBetweenRedraws(), чтобы ограничить количество повторных отображений и метод setRedrawFrequency(), чтобы отображать повторно каждые N итераций. По умолчанию, частота повторного отображения - 100мс или 10% вашего max.

Если вы не знаете точное количество шагов в продвижении, установите резонное значение, а потом вызовите метод setMaxSteps(), чтобы обновить его так, как надо:

1
2
3
4
5
// начните с индикатора выполнения на 50 единиц
$progressBar = new ProgressBar($output, 50);

// только что было создано сложное задание; увеличьте индикатор выполнения до 200 единиц
$progressBar->setMaxSteps(200);

Другим решением будет просто опусить аргумент шагов при создании экземпляра ProgressBar:

1
$progressBar = new ProgressBar($output);

Тогда прогресс будет отображён в виде троббера:

1
2
3
4
5
6
7
8
9
# нет максимального количества шагов (отображает его, как троббер)
    0 [>---------------------------]
    5 [----->----------------------]
    5 [============================]

# максимальное количество шагов определено
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%

Tip

Альтернативой этому будет использование Індикатор прогресса вместо индикатора выполнения.

Каждый раз по завершению задачи, не забудьте вызвать finish(), чтобы гарантировать, что индикатор выполнения обновлён до 100% завершения.

Note

Если вы хотите вывести что-то во время работы индикатора выполнения, вначале вызовите clear(). После того, как вы закончите, вызовите display(), чтобы отобразить индикатор выполнения снова.

Если информация индикатора выполнения хранится в итерируемой переменной (такой как массив или PHP-генератор), вы можете использовать метод iterate(), который начинает, продолжает и заканчивает индикатор выполнения автоматически:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use Symfony\Component\Console\Helper\ProgressBar;

$progressBar = new ProgressBar($output);

// $iterable может быть массивом
$iterable = [1, 2];
foreach ($progressBar->iterate($iterable) as $value) {
    // ... сделать какую-то работу
}

// или генератором
function iterable() { yield 1; yield 2; ... };
foreach ($progressBar->iterate(iterable()) as $value) {
    // ... сделать какую-то работу
}

Предыдущий код выведет:

1
2
3
0/2 [>---------------------------]   0%
1/2 [==============>-------------]  50%
2/2 [============================] 100%

Настройка индикатора выполнения

Встроенные форматы

По умолчанию, инфрмация, отображённая в индикаторе выполнения, зависит от текущего уровня детализации экземпляра OutputInterface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# OutputInterface::VERBOSITY_NORMAL (CLI без флажка детализации)
 0/3 [>---------------------------]   0%
 1/3 [=========>------------------]  33%
 3/3 [============================] 100%

# OutputInterface::VERBOSITY_VERBOSE (-v)
 0/3 [>---------------------------]   0%  1 сек
 1/3 [=========>------------------]  33%  1 сек
 3/3 [============================] 100%  1 сек

# OutputInterface::VERBOSITY_VERY_VERBOSE (-vv)
 0/3 [>---------------------------]   0%  1 сек
 1/3 [=========>------------------]  33%  1 сек
 3/3 [============================] 100%  1 сек

# OutputInterface::VERBOSITY_DEBUG (-vvv)
 0/3 [>---------------------------]   0%  1 сек/1 сек  1.0 MB
 1/3 [=========>------------------]  33%  1 сек/1 сек  1.0 MB
 3/3 [============================] 100%  1 сек/1 сек  1.0 MB

Note

Если вы вызовtnt команду с флажком заглушения (-q), то индикатор выполнения не будет отображён.

Вместо того, чтобы полагаться на режим детализации текущей команды, вы также можете форсировать формат через setFormat():

1
$progressBar->setFormat('verbose');

Следующие форматы являются встроенными:

  • normal
  • verbose
  • very_verbose
  • debug

Если вы не установите количество шагов для вашего индикатора выполнения, используйте варианты _nomax:

  • normal_nomax
  • verbose_nomax
  • very_verbose_nomax
  • debug_nomax

Пользовательские форматы

Вместо использования встроенных форматов, вы также можете установить собственные:

1
$progressBar->setFormat('%bar%');

Это устанавливает формат, чтобы отображать только сам индикатор:

1
2
3
>---------------------------
=========>------------------
============================

Формат индикатора выполнения - это строка, содержащая заполнители (имя, заканчивающееся символом %); заполнители заменяются в зависимости от текущего прогресса индикатора.
Вот список встроенных заполнителей:

  • current: Текущий шаг;
  • max: Максимальное количество шагов (или 0, если максимум не определён);
  • bar: Сам индикатор;
  • percent: Процент выполнения (не доступен, если не определён максимум);
  • elapsed: Время, прошедшее с начала запуска индикатора выполнения;
  • remaining: Оставшееся время для выполнения задачи (не доступно, если не определён максимум);
  • estimated: Предполагаемое время для выполнения задачи (не доступно, если не определён максимум);
  • memory: Текущее исползование памяти;
  • message: используется для отображения произвольных сообщений в индикаторе (объсняется ниже).

Поля времени elapsed, remaining и estimated отображаются с точностью до 2.
Это означает, что 172799 секунд отображаются как 1 день, 23 часа вместо 1 день, 23 часа, 59 минут, 59 секунд.

Например, вот как вы можете установить такой же формат, как и debug:

1
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %elapsed:16s%/%estimated:-16s% %memory:6s%');

Заметили часть :6s, добавленную к некоторым заполнителям? Это то, как вы можете настроить внешний вид индикатора (форматирование и расположение). Часть после двоеточия (:) используется для установки формата строки sprintf.

Вместо установки формата для заданного экземпляра индикатора выполнения, вы можете также определить глобальные форматы:

1
2
3
4
ProgressBar::setFormatDefinition('minimal', 'Progress: %percent%%');

$progressBar = new ProgressBar($output, 3);
$progressBar->setFormat('minimal');

Этот код определяет новый формат minimal, который вы потом можете использовать для ваших индикаторов выполнения:

1
2
3
Progress: 0%
Progress: 33%
Progress: 100%

Tip

Почти всегда лучше переопределить встроенные форматы, чем создавать новые, так как это позволяет отображение разниваться автоматически, основываясь на флажке детализации команды.

При определении нового стиля, содержащего заполнители, которые доступны только при заданном максимальном количестве шагов, вы должны создать вариант _nomax:

1
2
3
4
5
ProgressBar::setFormatDefinition('minimal', '%percent%% %remaining%');
ProgressBar::setFormatDefinition('minimal_nomax', '%percent%%');

$progressBar = new ProgressBar($output);
$progressBar->setFormat('minimal');

При отображении индикатора выполнения, формат будет автоматически установлен, как minimal_nomax, если шкала не имеет максимального количества шагов, как в примере выше.

Tip

Формат может содержать любые валидные коды ANSI, а также использовfm специфический способ Symfony для установки цветов:

1
2
3
4
ProgressBar::setFormatDefinition(
    'minimal',
    '<info>%percent%</info>\033[32m%\033[0m <fg=white;bg=blue>%remaining%</>'
);

Note

Формат может занимать больше одной строчки; это очень полезно, когда вы хотите отобразить больше контекстной нформации рядом с индикатором выполнения (см. пример в начале этой статьи).

Настройки индикатора

Среди заполнителей, bar немного выделяется, так как все символы, используемые для его отображения, могут быть настроены:

1
2
3
4
5
6
7
8
9
10
11
// законченная часть индикатора
$progressBar->setBarCharacter('<comment>=</comment>');

// незаконченная часть индикатора
$progressBar->setEmptyBarCharacter(' ');

// символ прогресса
$progressBar->setProgressCharacter('|');

// ширина индикатора
$progressBar->setBarWidth(50);

Caution

По причинам производительности, Symfony повторно отображает экран каждые 100мс. Если это слишком медленно или быстро для вашего приложения, используйте методы minSecondsBetweenRedraws() и maxSecondsBetweenRedraws():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$progressBar = new ProgressBar($output, 50000);
$progressBar->start();

// это повторно отображает экран каждые 100 итераций, но устанавливает дополнительные ограничения:
// не отображать экран повторно медленнее, чем раз в 200 мс (0.2) или быстрее, чем раз в 200 мс (0.1)
$progressBar->setRedrawFrequency(100);
$progressBar->maxSecondsBetweenRedraws(0.2);
$progressBar->minSecondsBetweenRedraws(0.1);

$i = 0;
while ($i++ < 50000) {
    // ... сделать какую-то работу

    $progressBar->advance();
}

Пользовательские заполнители

Если вы хотите отобразить некоторую информацию, зависящую от отображения индикатора выполнения, недоступную в списке встроенных заполнителей, вы можете создать собственный. Давайте посмотрим, как вы можете создать заполнитель remaining_steps, отображающий количество оставшихся шагов:

1
2
3
4
5
6
7
// Это определение зарегистрировано глобально для всех экземпляров ProgressBar
ProgressBar::setPlaceholderFormatterDefinition(
    'remaining_steps',
    function (ProgressBar $progressBar, OutputInterface $output): int {
        return $progressBar->getMaxSteps() - $progressBar->getProgress();
    }
);

Также возможно установить форматировщики заполнителей для каждого экземпляра ProgressBar с помощью метода setPlaceholderFormatter:

1
2
3
4
5
$progressBar = new ProgressBar($output, 3, 0);
$progressBar->setFormat('%countdown% [%bar%]');
$progressBar->setPlaceholderFormatter('countdown', function (ProgressBar $progressBar) {
    return $progressBar->getMaxSteps() - $progressBar->getProgress();
});

Пользовательские сообщения

Индикаторы выполнения определяют заполнитель под названием message для отображения произвольных сообщений. Однако, ни один из встроенных фрматов не включает в себя этот заполнитель, поэтому до отображения этих сообщений вы должны определить ваш собственный пользовательский формат:

1
2
3
4
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');

$progressBar = new ProgressBar($output, 100);
$progressBar->setFormat('custom');

Теперь, используйте метод setMessage(), чтобы установить значение заполнителя %message% до отображения индикатора выполнения:

1
2
3
4
5
6
7
8
// ...
$progressBar->setMessage('Start');
$progressBar->start();
// 0/100 -- Start

$progressBar->advance();
$progressBar->setMessage('Задача в процессе выполнения...');
// 1/100 -- Задача в процессе выполнения...

Сообщения могут также быть объединены с пользовательскими заполнителями. В этом примере, индикатор выполнения использует заполнители %message% и %filename%:

1
2
3
4
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message% (%filename%)');

$progressBar = new ProgressBar($output, 100);
$progressBar->setFormat('custom');

Метод setMessage() принимает второй необязательный аргумент для установки значения пользовательского заполнителя:

1
2
3
4
5
6
7
8
// ...
// $files = array('client-001/invoices.xml', '...');
foreach ($files as $filename) {
    $progressBar->setMessage('Импорт счетов...');
    $progressBar->setMessage($filename, 'filename');
    $progressBar->advance();
    // 2/100 -- Импорт счетов... (client-001/invoices.xml)
}

Отображение нескольких индикаторов выполнения

При использовании Разделов вывода консоли , возможно отображать несклько индикаторов выполнения одновременно и изменять их прогресс независимо друг от друга:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$section1 = $output->section();
$section2 = $output->section();

$progress1 = new ProgressBar($section1);
$progress2 = new ProgressBar($section2);

$progress1->start(100);
$progress2->start(100);

$i = 0;
while (++$i < 100) {
    $progress1->advance();

    if ($i % 2 === 0) {
        $progress2->advance(4);
    }

    usleep(50000);
}

После нескольких итераций вывод в терминале будет выглядеть так:

1
2
34/100 [=========>------------------]  34%
68/100 [===================>--------]  68%