Skip to content
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

GDALTermProgress: display estimated remaining time for tasks >= 10 seconds #11152

Merged
merged 4 commits into from
Oct 29, 2024
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
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the "modern" version would be std::chrono::system_clock::now() but any advantage in this case is not not obvious...

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;
}
Loading