Skip to content

Commit

Permalink
GDALTermProgress: display estimated remaining time for tasks >= 10 se…
Browse files Browse the repository at this point in the history
…conds

Refreshed every tick (so 2.5%)

(only on interactive terminals. Tested with bash on Linux and cmd on Windows,
but should work for all reasonable terminals)

```
Before 5 seconds:
0...10...20...

After 5 seconds:
0...10...20...30...40...50                           - estimated remaining time: 00:00:07

Upon completion:
0...10...20...30...40...50...60...70...80...90...100 - done in 00:00:13.
```

Fixes #11100

Co-authored-by: Daniel Baston <[email protected]>
  • Loading branch information
rouault and dbaston committed Oct 28, 2024
1 parent 1a3335b commit ae153d0
Showing 1 changed file with 121 additions and 8 deletions.
129 changes: 121 additions & 8 deletions port/cpl_progress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#include <cmath>
#include <cstdio>
#include <ctime>

#include <algorithm>

Expand Down Expand Up @@ -163,6 +164,31 @@ void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
CPLFree(pData);
}

/************************************************************************/
/* GDALTermProgressWidth() */
/************************************************************************/

static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
{
int nWidth = 0;
for (int i = 0; i <= nMaxTicks; i++)
{
if (i % nMajorTickSpacing == 0)
{
int nPercent = (i * 100) / nMaxTicks;
do
{
nWidth++;
} while (nPercent /= 10);
}
else
{
nWidth += 1;
}
}
return nWidth;
}

/************************************************************************/
/* GDALTermProgress() */
/************************************************************************/
Expand All @@ -179,6 +205,11 @@ void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
0...10...20...30...40...50...60...70...80...90...100 - done.
\endverbatim
* Starting with GDAL 3.11, for tasks estimated to take more than 10 seconds,
* an estimated remaining time is also displayed at the end. And for tasks
* taking more than 5 seconds to complete, the total time is displayed upon
* completion.
*
* Every 2.5% of progress another number or period is emitted. Note that
* GDALTermProgress() uses internal static data to keep track of the last
* percentage reported and will get confused if two terminal based progress
Expand All @@ -200,30 +231,112 @@ int CPL_STDCALL GDALTermProgress(double dfComplete,
CPL_UNUSED const char *pszMessage,
CPL_UNUSED void *pProgressArg)
{
const int nThisTick =
std::min(40, std::max(0, static_cast<int>(dfComplete * 40.0)));
constexpr int MAX_TICKS = 40;
constexpr int MAJOR_TICK_SPACING = 4;
constexpr int LENGTH_OF_0_TO_100_PROGRESS =
GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);

const int nThisTick = std::min(
MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));

// Have we started a new progress run?
static int nLastTick = -1;
if (nThisTick < nLastTick && nLastTick >= 39)
static time_t nStartTime = 0;
// whether estimated remaining time is displayed
static bool bETADisplayed = false;
// number of characters displayed during last progress call
static int nCharacterCountLastTime = 0;
// maximum number of characters displayed during previous calls
static int nCharacterCountMax = 0;
if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
{
bETADisplayed = false;
nLastTick = -1;
nCharacterCountLastTime = 0;
nCharacterCountMax = 0;
}

if (nThisTick <= nLastTick)
return TRUE;

const time_t nCurTime = time(nullptr);
if (nLastTick < 0)
nStartTime = nCurTime;

constexpr int MIN_DELAY_FOR_ETA = 5; // in seconds
if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
dfComplete < 0.5)
{
static bool bIsTTY = CPLIsInteractive(stdout);
bETADisplayed = bIsTTY;
}
if (bETADisplayed)
{
for (int i = 0; i < nCharacterCountLastTime; ++i)
fprintf(stdout, "\b");
nLastTick = -1;
nCharacterCountLastTime = 0;
}

while (nThisTick > nLastTick)
{
++nLastTick;
if (nLastTick % 4 == 0)
fprintf(stdout, "%d", (nLastTick / 4) * 10);
if (nLastTick % MAJOR_TICK_SPACING == 0)
{
const int nPercent = (nLastTick * 100) / MAX_TICKS;
nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
}
else
fprintf(stdout, ".");
{
nCharacterCountLastTime += fprintf(stdout, ".");
}
}

if (nThisTick == 40)
fprintf(stdout, " - done.\n");
if (nThisTick == MAX_TICKS)
{
nCharacterCountLastTime += fprintf(stdout, " - done");
if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
{
const int nEllapsed = static_cast<int>(nCurTime - nStartTime);
const int nHours = nEllapsed / 3600;
const int nMins = (nEllapsed % 3600) / 60;
const int nSecs = nEllapsed % 60;
nCharacterCountLastTime +=
fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs);
for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
nCharacterCountLastTime += fprintf(stdout, " ");
}
else
{
fprintf(stdout, ".");
}
fprintf(stdout, "\n");
}
else
{
if (bETADisplayed)
{
for (int i = nCharacterCountLastTime;
i < LENGTH_OF_0_TO_100_PROGRESS; ++i)
nCharacterCountLastTime += fprintf(stdout, " ");

const double dfETA =
(nCurTime - nStartTime) * (1.0 / dfComplete - 1);
const int nETA = static_cast<int>(dfETA + 0.5);
const int nHours = nETA / 3600;
const int nMins = (nETA % 3600) / 60;
const int nSecs = nETA % 60;
nCharacterCountLastTime +=
fprintf(stdout, " - estimated remaining time: %02d:%02d:%02d",
nHours, nMins, nSecs);
for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
nCharacterCountLastTime += fprintf(stdout, " ");
}
fflush(stdout);
}

if (nCharacterCountLastTime > nCharacterCountMax)
nCharacterCountMax = nCharacterCountLastTime;

return TRUE;
}

0 comments on commit ae153d0

Please sign in to comment.