-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[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
Conversation
d6fd4c3
to
aa9ece5
Compare
A redraw should be triggered every x ms, and everything y %, whichever comes first. Would be the best IMHO. |
TBH I don't see how that's useful. Anyway, that would be separate option. Neither old nor new option is based on %. So I would leave that to separate PR if someone wants that. |
By "%" I mean "step" of course. The display should be updated whenever the bar grows by more than Y "pixels", or has been redrawn since 1s. That's useful for DX, showing something happens. |
Yes so set it to redraw each 0.1 seconds and be done with it. What's the use case when that's not enough? This PR solves the issue when you set $max to high value like 141977190. Advancing by 1% would mean lot of steps and user would be stuck seeing progressbar in same state if you calculate redrawing based on percentage / pixels. Also, calculating %/pixels requires to know $max value. |
aa9ece5
to
1e3aa9b
Compare
that's not enough with fast moving progress bars, where this strategy creates visual saccades / is jerky (while the current step-based logic doesn't) - so no, it's not enough in all cases |
I disagree that 10FPS is jerky for progress bar, no matter the speed of advancing. But you are free to set higher frequency. Also default value in Symfony 5 can be point of discussion. I don't see how is it useful if progress bar redraws itself more often than human eye can see. If progress bar goes from 0 to 100% in less than 100ms/10ms/1ms, do you really consider it useful to show each step all the way? There is a point when it's useless to redraw progress bar because you won't notice it changed in between time interval anyway. |
Yes, 100ms is way too slow for the eye. 40ms is the absolute minimum visual refresh period (24fps). |
You won't have to care about that in Symfony 5, where there will be default FPS set. You also won't have to then care about other problems I mentioned, like calculating percentage (which is impossible to do if $max is unknown), negative performance impact and unresponsive progress bar. That said, I don't care so much about deprecating it, it's your codebase. I consider time-based frequency superior on all fronts, but will leave old frequency in place if symfony team decides. If you want to redraw based on whichever condition is triggered first, I would need you to consider these things first though:
Before doing any more work around this, I would also like to hear other opinions first. Thanks! |
5f1d17c
to
e65af04
Compare
Bumping this, feature freeze is coming. Can somebody take a look? |
e65af04
to
55e0109
Compare
I've got feedback people don't really understand what is this for. So I guess that's why is this not gaining any traction. I'll try to simplify explanation what is this for then: First, let's ask what is redraw frequency in ProgressBar for, why would you need to adjust it? Several reasons:
Let's show example: $maxSteps = 100000000;
$redrawFrequency = 1000000;
$progressBar = new ProgressBar(new ConsoleOutput(), $maxSteps);
$progressBar->setRedrawFrequency($redrawFrequency);
for ($i=0; $i<= $maxSteps; $i += 100) {
$progressBar->setProgress($i);
} This is the output on my machine, might be different on yours: Now let's set redrawFrequency wrong: - $redrawFrequency = 1000000;
+ $redrawFrequency = 1; Now, bear in mind these are GIFs, they don't have much FPS. Normally, you will see much more flickering. But this is good enough, you see plenty flickering even there, in second GIF. So, what can you see in these GIFs?
Now, you understand importance of setting correct redraw frequency. So, what am I solving with this PR?
Real use case:I have a project called stdinho which uses ProgressBar to show progress of stdin/stdout streaming speed. Since input is via stdin, program has no idea about size it will get beforehand. I could estimate redraw frequency, if I knew speed of streaming. This speed is very different depending if I/O is via network, if server is slow, what type of disk is used etc. So it's impossible to set it correctly and setting it incorrectly have major performance penalty for speed of this streaming. So I had to give up on Symfony ProgressBar, I copied it and did changes I needed to its code directly. |
55e0109
to
fcc028b
Compare
If we merge this feature, can't it be implemented in a way that does not delete the old API (e.g. by creating a new class that internally uses the existing one)? I see a lot of use cases that work pretty well with the current approach and do not need to be rewritten it not entirely necessary. |
I mean, old API don't have to be deleted, but the way I see it, it just becomes redundant. Old frequency is more limited and more easy to burn yourself by setting wrong frequency. Also, name of old API is wrong for whole time, because frequency is usually dependent on time. From wiki:
Old API lies. It isn't dependent on time. New frequency conforms to definition. We can also ease transition by setting default argument now, so all that users need to do in 4.x is just remove setRedrawFrequency call. ProgressBar is very human-facing part and humans don't care so much if old and new frequency is exactly same, so this isn't covered via BC promise. We can also not remove setRedrawFrequency, but add to it second, boolean argument which signals time-based frequency. Deprecation will complain if it isn't set, but will stop complaining in Symfony 5 and just consider input to be time-based frequency. This means applications will keep working with no changes. Downside is if given argument isn't adjusted, ProgressBar will be redrawn much less frequently than intended originally, so not sure if this is a way. We can also play a long game and just deprecate calling setRedrawFrequency with float value. And in Symfony 5 it will be allowed again, but will mean this is time-based frequency. There are lot of options how to handle this, someone just needs to decide what to do. |
bump (will rebase when it's clear how to move this forward) |
@ostrolucky the main focus of the core team these days is on finishing 4.1, as the release is due in approximately 1 week. We'll get back to focus to all next PRs after it |
Would approach of introducing new class // edit: forget it that won't work, as ProgressBar does multiple write/writeln writes when redrawing |
e4e7001
to
bf3cca3
Compare
40aab42
to
b68e5b3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Works for me as-is, changing the default can be discussed later on.
What about this added patch? It works well with your example: --- a/src/Symfony/Component/Console/Helper/ProgressBar.php
+++ b/src/Symfony/Component/Console/Helper/ProgressBar.php
@@ -31,7 +31,7 @@ final class ProgressBar
private $progressChar = '>';
private $format;
private $internalFormat;
- private $redrawFreq = 1;
+ private $redrawFreq;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
@@ -318,8 +318,9 @@ final class ProgressBar
$step = 0;
}
- $prevPeriod = (int) ($this->step / $this->redrawFreq);
- $currPeriod = (int) ($step / $this->redrawFreq);
+ $redrawFreq = $this->redrawFreq ?? (($this->max ?: 20) / 20);
+ $prevPeriod = (int) ($this->step / $redrawFreq);
+ $currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
$timeInterval = microtime(true) - $this->lastWriteTime; |
b68e5b3
to
bbd64ab
Compare
bbd64ab
to
6381c4b
Compare
@ostrolucky I push forced on your branch to rebase on latest 4.4, add a CHANGELOG, and fix what I understood from your last GIF. Am I right that the issue you wanted to showcase was the cursor moving back when preventRedrawFasterThan is used? If yes, that's fixed. If not, please share the exact code you ran because right now this is cryptic to me. To me, this PR is ready:
|
6381c4b
to
cb08298
Compare
…lowerThan() methods
cb08298
to
83edac3
Compare
Thank you @ostrolucky. |
…nd 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
…0ms by default (nicolas-grekas) This PR was merged into the 4.4 branch. Discussion ---------- [Console] don't redraw progress bar more than every 100ms by default | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | no (behavior change) | BC breaks? | no (behavior change) | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Follow up of symfony/symfony#26339 Looks like something we can and should do on 4.4 to me. Commits ------- df551e945c [Console] don't redraw progress bar more than every 100ms by default
…0ms by default (nicolas-grekas) This PR was merged into the 4.4 branch. Discussion ---------- [Console] don't redraw progress bar more than every 100ms by default | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | no (behavior change) | BC breaks? | no (behavior change) | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Follow up of #26339 Looks like something we can and should do on 4.4 to me. Commits ------- df551e9 [Console] don't redraw progress bar more than every 100ms by default
…ncy (javiereguiluz) This PR was squashed before being merged into the 4.4 branch (closes #33732). Discussion ---------- [Console] Rename some methods related to redraw frequency | Q | A | ------------- | --- | Branch? | 4.4 | Bug fix? | no | New feature? | no | Deprecations? | no | Tickets | - | License | MIT | Doc PR | - In #26339 we added `preventRedrawFasterThan()` and `forceRedrawSlowerThan()`. While merging the docs for them (symfony/symfony-docs#12364) I thought that the method names are a bit hard to understand. In this PR I propose a renaming for your consideration. Thanks! In the following example, we want to update the progress bar every 100 iterations, but not faster than 100ms or slower than 200ms. **Before** ```php $progressBar = new ProgressBar($output, 50000); $progressBar->start(); $progressBar->setRedrawFrequency(100); $progressBar->preventRedrawFasterThan(0.1); $progressBar->forceRedrawSlowerThan(0.2); ``` **After** ```php $progressBar = new ProgressBar($output, 50000); $progressBar->start(); $progressBar->setRedrawFrequency(100); $progressBar->maxRefreshInterval(0.1); $progressBar->minRefreshInterval(0.2); ``` Commits ------- e6ee7b0 [Console] Rename some methods related to redraw frequency
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.)