Skip to content

[Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods #26339

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2019
Merged
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
5 changes: 5 additions & 0 deletions src/Symfony/Component/Console/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

4.4.0
-----

* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`

4.3.0
-----

Expand Down
58 changes: 50 additions & 8 deletions src/Symfony/Component/Console/Helper/ProgressBar.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ final class ProgressBar
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
Expand All @@ -51,7 +55,7 @@ final class ProgressBar
* @param OutputInterface $output An OutputInterface instance
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, int $max = 0)
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
Expand All @@ -61,12 +65,17 @@ public function __construct(OutputInterface $output, int $max = 0)
$this->setMaxSteps($max);
$this->terminal = new Terminal();

if (0 < $minSecondsBetweenRedraws) {
$this->redrawFreq = null;
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
}

if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;

// set a reasonable redraw frequency so output isn't flooded
$this->setRedrawFrequency($max / 10);
$this->redrawFreq = null;
}

$this->startTime = time();
Expand Down Expand Up @@ -183,6 +192,11 @@ public function getProgressPercent(): float
return $this->percent;
}

public function getBarOffset(): int
{
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
}

public function setBarWidth(int $size)
{
$this->barWidth = max(1, $size);
Expand Down Expand Up @@ -238,9 +252,19 @@ public function setFormat(string $format)
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency(int $freq)
public function setRedrawFrequency(?int $freq)
{
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}

public function preventRedrawFasterThan(float $intervalInSeconds): void
{
$this->minSecondsBetweenRedraws = $intervalInSeconds;
}

public function forceRedrawSlowerThan(float $intervalInSeconds): void
{
$this->redrawFreq = max($freq, 1);
$this->maxSecondsBetweenRedraws = $intervalInSeconds;
}

/**
Expand Down Expand Up @@ -305,11 +329,27 @@ public function setProgress(int $step)
$step = 0;
}

$prevPeriod = (int) ($this->step / $this->redrawFreq);
$currPeriod = (int) ($step / $this->redrawFreq);
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
$prevPeriod = (int) ($this->step / $redrawFreq);
$currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
if ($prevPeriod !== $currPeriod || $this->max === $step) {
$timeInterval = microtime(true) - $this->lastWriteTime;

// Draw regardless of other limits
if ($this->max === $step) {
$this->display();

return;
}

// Throttling
if ($timeInterval < $this->minSecondsBetweenRedraws) {
return;
}

// Draw each step period, but not too late
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
$this->display();
}
}
Expand Down Expand Up @@ -413,8 +453,10 @@ private function overwrite(string $message): void
}

$this->firstRun = false;
$this->lastWriteTime = microtime(true);

$this->output->write($message);
++$this->writeCount;
}

private function determineBestFormat(): string
Expand All @@ -436,7 +478,7 @@ private static function initPlaceholderFormatters(): array
{
return [
'bar' => function (self $bar, OutputInterface $output) {
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
$completeBars = $bar->getBarOffset();
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
Expand Down
54 changes: 54 additions & 0 deletions src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -944,4 +944,58 @@ public function testBarWidthWithMultilineFormat()
$this->assertEquals(5, $bar->getBarWidth(), stream_get_contents($output->getStream()));
putenv('COLUMNS=120');
}

public function testForceRedrawSlowerThan(): void
{
$bar = new ProgressBar($output = $this->getOutputStream());
$bar->setRedrawFrequency(4); // disable step based redraws
$bar->start();
$bar->setProgress(1); // No treshold hit, no redraw
$bar->forceRedrawSlowerThan(2);
sleep(1);
$bar->setProgress(2); // Still no redraw because redraw is forced after 2 seconds only
sleep(1);
$bar->setProgress(3); // 1+1 = 2 -> redraw finally
$bar->setProgress(4); // step based redraw freq hit, redraw even without sleep
$bar->setProgress(5); // No treshold hit, no redraw
$bar->preventRedrawFasterThan(3);
sleep(2);
$bar->setProgress(6); // No redraw even though 2 seconds passed. Throttling has priority
$bar->preventRedrawFasterThan(2);
$bar->setProgress(7); // Throttling relaxed, draw

rewind($output->getStream());
$this->assertEquals(
' 0 [>---------------------------]'.
$this->generateOutput(' 3 [--->------------------------]').
$this->generateOutput(' 4 [---->-----------------------]').
$this->generateOutput(' 7 [------->--------------------]'),
stream_get_contents($output->getStream())
);
}

public function testPreventRedrawFasterThan()
{
$bar = new ProgressBar($output = $this->getOutputStream());
$bar->setRedrawFrequency(1);
$bar->preventRedrawFasterThan(1);
$bar->start();
$bar->setProgress(1); // Too fast, should not draw
sleep(1);
$bar->setProgress(2); // 1 second passed, draw
$bar->preventRedrawFasterThan(2);
sleep(1);
$bar->setProgress(3); // 1 second passed but we changed threshold, should not draw
sleep(1);
$bar->setProgress(4); // 1+1 seconds = 2 seconds passed which conforms threshold, draw
$bar->setProgress(5); // No treshold hit, no redraw

rewind($output->getStream());
$this->assertEquals(
' 0 [>---------------------------]'.
$this->generateOutput(' 2 [-->-------------------------]').
$this->generateOutput(' 4 [---->-----------------------]'),
stream_get_contents($output->getStream())
);
}
}