Skip to content

Commit

Permalink
Merge pull request #11152 from rouault/fix_11100
Browse files Browse the repository at this point in the history
GDALTermProgress: display estimated remaining time for tasks >= 10 seconds
  • Loading branch information
rouault authored Oct 29, 2024
2 parents d458bd0 + 67f18e2 commit 71535b5
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 40 deletions.
8 changes: 1 addition & 7 deletions apps/gdallocationinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,6 @@

#include <cctype>

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

/************************************************************************/
/* GetSRSAsWKT */
/************************************************************************/
Expand Down Expand Up @@ -300,7 +294,7 @@ MAIN_START(argc, argv)
if (std::isnan(dfGeoX))
{
// Is it an interactive terminal ?
if (isatty(static_cast<int>(fileno(stdin))))
if (CPLIsInteractive(stdin))
{
if (!osSourceSRS.empty())
{
Expand Down
8 changes: 1 addition & 7 deletions apps/gdaltransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@
#include "ogr_srs_api.h"
#include "commonutils.h"

#ifdef _WIN32
#include <io.h>
#else
#include <unistd.h>
#endif

/************************************************************************/
/* Usage() */
/************************************************************************/
Expand Down Expand Up @@ -359,7 +353,7 @@ MAIN_START(argc, argv)
if (!bCoordOnCommandLine)
{
// Is it an interactive terminal ?
if (isatty(static_cast<int>(fileno(stdin))))
if (CPLIsInteractive(stdin))
{
if (pszSrcFilename != nullptr)
{
Expand Down
4 changes: 2 additions & 2 deletions apps/gdalwarp_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2552,8 +2552,8 @@ static GDALDatasetH GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS,
{
CPLString osMsg;
osMsg.Printf("Processing %s [%d/%d]",
GDALGetDescription(pahSrcDS[iSrc]), iSrc + 1,
nSrcCount);
CPLGetFilename(GDALGetDescription(pahSrcDS[iSrc])),
iSrc + 1, nSrcCount);
return pfnExternalProgress((iSrc + dfComplete) / nSrcCount,
osMsg.c_str(), pExternalProgressData);
}
Expand Down
12 changes: 1 addition & 11 deletions frmts/grib/gribdataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@
#include <fcntl.h>
#endif

#ifndef _WIN32
#include <unistd.h> // isatty()
#else
#include <io.h> // _isatty()
#endif

#include <algorithm>
#include <set>
#include <string>
Expand Down Expand Up @@ -901,11 +895,7 @@ static bool IsGdalinfoInteractive()
{
static const bool bIsGdalinfoInteractive = []()
{
#ifndef _WIN32
if (isatty(static_cast<int>(fileno(stdout))))
#else
if (_isatty(_fileno(stdout)))
#endif
if (CPLIsInteractive(stdout))
{
std::string osPath;
osPath.resize(1024);
Expand Down
27 changes: 27 additions & 0 deletions port/cpl_conv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
#include <xlocale.h> // for LC_NUMERIC_MASK on MacOS
#endif

#ifdef _WIN32
#include <io.h> // _isatty
#else
#include <unistd.h> // isatty
#endif

#ifdef DEBUG_CONFIG_OPTIONS
#include <set>
#endif
Expand Down Expand Up @@ -3493,3 +3499,24 @@ CPLConfigOptionSetter::~CPLConfigOptionSetter()
}

//! @endcond

/************************************************************************/
/* CPLIsInteractive() */
/************************************************************************/

/** Returns whether the provided file refers to a terminal.
*
* This function is a wrapper of the ``isatty()`` POSIX function.
*
* @param f File to test. Typically stdin, stdout or stderr
* @return true if it is an open file referring to a terminal.
* @since GDAL 3.11
*/
bool CPLIsInteractive(FILE *f)
{
#ifndef _WIN32
return isatty(static_cast<int>(fileno(f)));
#else
return _isatty(_fileno(f));
#endif
}
6 changes: 6 additions & 0 deletions port/cpl_conv.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,12 @@ void CPLCleanupSetlocaleMutex(void);
*/
int CPL_DLL CPLIsPowerOfTwo(unsigned int i);

/* -------------------------------------------------------------------- */
/* Terminal related */
/* -------------------------------------------------------------------- */

bool CPL_DLL CPLIsInteractive(FILE *f);

CPL_C_END

/* -------------------------------------------------------------------- */
Expand Down
6 changes: 1 addition & 5 deletions port/cpl_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@

#include "cpl_error.h"

#ifndef _WIN32
#include <unistd.h> // isatty()
#endif

#include <cstdarg>
#include <cstdio>
#include <cstdlib>
Expand Down Expand Up @@ -1052,7 +1048,7 @@ void CPL_STDCALL CPLDefaultErrorHandler(CPLErr eErrClass, CPLErrorNum nError,
#ifndef _WIN32
CPLErrorContext *psCtx = CPLGetErrorContext();
if (psCtx != nullptr && !IS_PREFEFINED_ERROR_CTX(psCtx) &&
fpLog == stderr && isatty(static_cast<int>(fileno(stderr))))
fpLog == stderr && CPLIsInteractive(stderr))
{
if (psCtx->bProgressMode)
{
Expand Down
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 71535b5

Please sign in to comment.