8000 feature #26339 [Console] Add ProgressBar::preventRedrawFasterThan() a… · symfony/symfony@c202e96 · GitHub
[go: up one dir, main page]

Skip to content

Commit c202e96

Browse files
committed
feature #26339 [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods (ostrolucky)
This PR was merged into the 4.4 branch. Discussion ---------- [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | TBA The way ProgressBar redraw frequency works currently requires to know speed of progress beforehand, which is impossible to know in some situations, e.g. when showing progress of download, or I/O speed. Setting frequency too low relative to progress speed throttles I/O speed and makes progress bar flicker too much, setting it too high makes progress bar unresponsive. Current behaviour IMHO undermines usefulness of ProgressBar. This is an attempt to replace this with more consistent experience, not requiring to know speed of progress.) Commits ------- 83edac3 [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods
2 parents e631806 + 83edac3 commit c202e96

File tree

3 files changed

+106
-9
lines changed

3 files changed

+106
-9
lines changed

src/Symfony/Component/Console/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ CHANGELOG
44
4.4.0
55
-----
66

7-
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
7+
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
8+
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
89

910
4.3.0
1011
-----

src/Symfony/Component/Console/Helper/ProgressBar.php

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ final class ProgressBar
3232
private $format;
3333
private $internalFormat;
3434
private $redrawFreq = 1;
35+
private $writeCount;
36+
private $lastWriteTime;
37+
private $minSecondsBetweenRedraws = 0;
38+
private $maxSecondsBetweenRedraws = 1;
3539
private $output;
3640
private $step = 0;
3741
private $max;
@@ -51,7 +55,7 @@ final class ProgressBar
5155
* @param OutputInterface $output An OutputInterface instance
5256
* @param int $max Maximum steps (0 if unknown)
5357
*/
54-
public function __construct(OutputInterface $output, int $max = 0)
58+
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0)
5559
{
5660
if ($output instanceof ConsoleOutputInterface) {
5761
$output = $output->getErrorOutput();
@@ -61,12 +65,17 @@ public function __construct(OutputInterface $output, int $max = 0)
6165
$this->setMaxSteps($max);
6266
$this->terminal = new Terminal();
6367

68+
if (0 < $minSecondsBetweenRedraws) {
69+
$this->redrawFreq = null;
70+
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
71+
}
72+
6473
if (!$this->output->isDecorated()) {
6574
// disable overwrite when output does not support ANSI codes.
6675
$this->overwrite = false;
6776

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

7281
$this->startTime = time();
@@ -183,6 +192,11 @@ public function getProgressPercent(): float
183192
return $this->percent;
184193
}
185194

195+
public function getBarOffset(): int
196+
{
197+
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
198+
}
199+
186200
public function setBarWidth(int $size)
187201
{
188202
$this->barWidth = max(1, $size);
@@ -238,9 +252,19 @@ public function setFormat(string $format)
238252
*
239253
* @param int|float $freq The frequency in steps
240254
*/
241-
public function setRedrawFrequency(int $freq)
255+
public function setRedrawFrequency(?int $freq)
256+
{
257+
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
258+
}
259+
260+
public function preventRedrawFasterThan(float $intervalInSeconds): void
261+
{
262+
$this->minSecondsBetweenRedraws = $intervalInSeconds;
263+
}
264+
265+
public function forceRedrawSlowerThan(float $intervalInSeconds): void
242266
{
243-
$this->redrawFreq = max($freq, 1);
267+
$this->maxSecondsBetweenRedraws = $intervalInSeconds;
244268
}
245269

246270
/**
@@ -305,11 +329,27 @@ public function setProgress(int $step)
305329
$step = 0;
306330
}
307331

308-
$prevPeriod = (int) ($this->step / $this->redrawFreq);
309-
$curr 1E0A Period = (int) ($step / $this->redrawFreq);
332+
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
333+
$prevPeriod = (int) ($this->step / $redrawFreq);
334+
$currPeriod = (int) ($step / $redrawFreq);
310335
$this->step = $step;
311336
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
312-
if ($prevPeriod !== $currPeriod || $this->max === $step) {
337+
$timeInterval = microtime(true) - $this->lastWriteTime;
338+
339+
// Draw regardless of other limits
340+
if ($this->max === $step) {
341+
$this->display();
342+
343+
return;
344+
}
345+
346+
// Throttling
347+
if ($timeInterval < $this->minSecondsBetweenRedraws) {
348+
return;
349+
}
350+
351+
// Draw each step period, but not too late
352+
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
313353
$this->display();
314354
}
315355
}
@@ -413,8 +453,10 @@ private function overwrite(string $message): void
413453
}
414454

415455
$this->firstRun = false;
456+
$this->lastWriteTime = microtime(true);
416457

417458
$this->output->write($message);
459+
++$this->writeCount;
418460
}
419461

420462
private function determineBestFormat(): string
@@ -436,7 +478,7 @@ private static function initPlaceholderFormatters(): array
436478
{
437479
return [
438480
'bar' => function (self $bar, OutputInterface $output) {
439-
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
481+
$completeBars = $bar->getBarOffset();
440482
$display = str_repeat($bar->getBarCharacter(), $completeBars);
441483
if ($completeBars < $bar->getBarWidth()) {
442484
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());

src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,4 +944,58 @@ public function testBarWidthWithMultilineFormat()
944944
$this->assertEquals(5, $bar->getBarWidth(), stream_get_contents($output->getStream()));
945945
putenv('COLUMNS=120');
946946
}
947+
948+
public function testForceRedrawSlowerThan(): void
949+
{
950+
$bar = new ProgressBar($output = $this->getOutputStream());
951+
$bar->setRedrawFrequency(4); // disable step based redraws
952+
$bar->start();
953+
$bar->setProgress(1); // No treshold hit, no redraw
954+
$bar->forceRedrawSlowerThan(2);
955+
sleep(1);
956+
$bar->setProgress(2); // Still no redraw because redraw is forced after 2 seconds only
957+
sleep(1);
958+
$bar->setProgress(3); // 1+1 = 2 -> redraw finally
959+
$bar->setProgress(4); // step based redraw freq hit, redraw even without sleep
960+
$bar->setProgress(5); // No treshold hit, no redraw
961+
$bar->preventRedrawFasterThan(3);
962+
sleep(2);
963+
$bar->setProgress(6); // No redraw even though 2 seconds passed. Throttling has priority
964+
$bar->preventRedrawFasterThan(2);
965+
$bar->setProgress(7); // Throttling relaxed, draw
966+
967+
rewind($output->getStream());
968+
$this->assertEquals(
969+
' 0 [>---------------------------]'.
970+
$this->generateOutput(' 3 [--->------------------------]').
971+
$this->generateOutput(' 4 [---->-----------------------]').
972+
$this->generateOutput(' 7 [------->--------------------]'),
973+
stream_get_contents($output->getStream())
974+
);
975+
}
976+
977+
public function testPreventRedrawFasterThan()
978+
{
979+
$bar = new ProgressBar($output = $this->getOutputStream());
980+
$bar->setRedrawFrequency(1);
981+
$bar->preventRedrawFasterThan(1);
982+
$bar->start();
983+
$bar->setProgress(1); // Too fast, should not draw
984+
sleep(1);
985+
$bar->setProgress(2); // 1 second passed, draw
986+
$bar->preventRedrawFasterThan(2);
987+
sleep(1);
988+
$bar->setProgress(3); // 1 second passed but we changed threshold, should not draw
989+
sleep(1);
990+
$bar->setProgress(4); // 1+1 seconds = 2 seconds passed which conforms threshold, draw
991+
$bar->setProgress(5); // No treshold hit, no redraw
992+
993+
rewind($output->getStream());
994+
$this->assertEquals(
995+
' 0 [>---------------------------]'.
996+
$this->generateOutput(' 2 [-->-------------------------]').
997+
$this->generateOutput(' 4 [---->-----------------------]'),
998+
stream_get_contents($output->getStream())
999+
);
1000+
}
9471001
}

0 commit comments

Comments
 (0)
0