Skip to content

Commit 71535b5

Browse files
authored
Merge pull request #11152 from rouault/fix_11100
GDALTermProgress: display estimated remaining time for tasks >= 10 seconds
2 parents d458bd0 + 67f18e2 commit 71535b5

File tree

8 files changed

+160
-40
lines changed

8 files changed

+160
-40
lines changed

apps/gdallocationinfo.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,6 @@
2626

2727
#include <cctype>
2828

29-
#ifdef _WIN32
30-
#include <io.h>
31-
#else
32-
#include <unistd.h>
33-
#endif
34-
3529
/************************************************************************/
3630
/* GetSRSAsWKT */
3731
/************************************************************************/
@@ -300,7 +294,7 @@ MAIN_START(argc, argv)
300294
if (std::isnan(dfGeoX))
301295
{
302296
// Is it an interactive terminal ?
303-
if (isatty(static_cast<int>(fileno(stdin))))
297+
if (CPLIsInteractive(stdin))
304298
{
305299
if (!osSourceSRS.empty())
306300
{

apps/gdaltransform.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,6 @@
3030
#include "ogr_srs_api.h"
3131
#include "commonutils.h"
3232

33-
#ifdef _WIN32
34-
#include <io.h>
35-
#else
36-
#include <unistd.h>
37-
#endif
38-
3933
/************************************************************************/
4034
/* Usage() */
4135
/************************************************************************/
@@ -359,7 +353,7 @@ MAIN_START(argc, argv)
359353
if (!bCoordOnCommandLine)
360354
{
361355
// Is it an interactive terminal ?
362-
if (isatty(static_cast<int>(fileno(stdin))))
356+
if (CPLIsInteractive(stdin))
363357
{
364358
if (pszSrcFilename != nullptr)
365359
{

apps/gdalwarp_lib.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2552,8 +2552,8 @@ static GDALDatasetH GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS,
25522552
{
25532553
CPLString osMsg;
25542554
osMsg.Printf("Processing %s [%d/%d]",
2555-
GDALGetDescription(pahSrcDS[iSrc]), iSrc + 1,
2556-
nSrcCount);
2555+
CPLGetFilename(GDALGetDescription(pahSrcDS[iSrc])),
2556+
iSrc + 1, nSrcCount);
25572557
return pfnExternalProgress((iSrc + dfComplete) / nSrcCount,
25582558
osMsg.c_str(), pExternalProgressData);
25592559
}

frmts/grib/gribdataset.cpp

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@
2727
#include <fcntl.h>
2828
#endif
2929

30-
#ifndef _WIN32
31-
#include <unistd.h> // isatty()
32-
#else
33-
#include <io.h> // _isatty()
34-
#endif
35-
3630
#include <algorithm>
3731
#include <set>
3832
#include <string>
@@ -901,11 +895,7 @@ static bool IsGdalinfoInteractive()
901895
{
902896
static const bool bIsGdalinfoInteractive = []()
903897
{
904-
#ifndef _WIN32
905-
if (isatty(static_cast<int>(fileno(stdout))))
906-
#else
907-
if (_isatty(_fileno(stdout)))
908-
#endif
898+
if (CPLIsInteractive(stdout))
909899
{
910900
std::string osPath;
911901
osPath.resize(1024);

port/cpl_conv.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@
6262
#include <xlocale.h> // for LC_NUMERIC_MASK on MacOS
6363
#endif
6464

65+
#ifdef _WIN32
66+
#include <io.h> // _isatty
67+
#else
68+
#include <unistd.h> // isatty
69+
#endif
70+
6571
#ifdef DEBUG_CONFIG_OPTIONS
6672
#include <set>
6773
#endif
@@ -3493,3 +3499,24 @@ CPLConfigOptionSetter::~CPLConfigOptionSetter()
34933499
}
34943500

34953501
//! @endcond
3502+
3503+
/************************************************************************/
3504+
/* CPLIsInteractive() */
3505+
/************************************************************************/
3506+
3507+
/** Returns whether the provided file refers to a terminal.
3508+
*
3509+
* This function is a wrapper of the ``isatty()`` POSIX function.
3510+
*
3511+
* @param f File to test. Typically stdin, stdout or stderr
3512+
* @return true if it is an open file referring to a terminal.
3513+
* @since GDAL 3.11
3514+
*/
3515+
bool CPLIsInteractive(FILE *f)
3516+
{
3517+
#ifndef _WIN32
3518+
return isatty(static_cast<int>(fileno(f)));
3519+
#else
3520+
return _isatty(_fileno(f));
3521+
#endif
3522+
}

port/cpl_conv.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ void CPLCleanupSetlocaleMutex(void);
309309
*/
310310
int CPL_DLL CPLIsPowerOfTwo(unsigned int i);
311311

312+
/* -------------------------------------------------------------------- */
313+
/* Terminal related */
314+
/* -------------------------------------------------------------------- */
315+
316+
bool CPL_DLL CPLIsInteractive(FILE *f);
317+
312318
CPL_C_END
313319

314320
/* -------------------------------------------------------------------- */

port/cpl_error.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@
1515

1616
#include "cpl_error.h"
1717

18-
#ifndef _WIN32
19-
#include <unistd.h> // isatty()
20-
#endif
21-
2218
#include <cstdarg>
2319
#include <cstdio>
2420
#include <cstdlib>
@@ -1052,7 +1048,7 @@ void CPL_STDCALL CPLDefaultErrorHandler(CPLErr eErrClass, CPLErrorNum nError,
10521048
#ifndef _WIN32
10531049
CPLErrorContext *psCtx = CPLGetErrorContext();
10541050
if (psCtx != nullptr && !IS_PREFEFINED_ERROR_CTX(psCtx) &&
1055-
fpLog == stderr && isatty(static_cast<int>(fileno(stderr))))
1051+
fpLog == stderr && CPLIsInteractive(stderr))
10561052
{
10571053
if (psCtx->bProgressMode)
10581054
{

port/cpl_progress.cpp

Lines changed: 121 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include <cmath>
1616
#include <cstdio>
17+
#include <ctime>
1718

1819
#include <algorithm>
1920

@@ -163,6 +164,31 @@ void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
163164
CPLFree(pData);
164165
}
165166

167+
/************************************************************************/
168+
/* GDALTermProgressWidth() */
169+
/************************************************************************/
170+
171+
static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
172+
{
173+
int nWidth = 0;
174+
for (int i = 0; i <= nMaxTicks; i++)
175+
{
176+
if (i % nMajorTickSpacing == 0)
177+
{
178+
int nPercent = (i * 100) / nMaxTicks;
179+
do
180+
{
181+
nWidth++;
182+
} while (nPercent /= 10);
183+
}
184+
else
185+
{
186+
nWidth += 1;
187+
}
188+
}
189+
return nWidth;
190+
}
191+
166192
/************************************************************************/
167193
/* GDALTermProgress() */
168194
/************************************************************************/
@@ -179,6 +205,11 @@ void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
179205
0...10...20...30...40...50...60...70...80...90...100 - done.
180206
\endverbatim
181207
208+
* Starting with GDAL 3.11, for tasks estimated to take more than 10 seconds,
209+
* an estimated remaining time is also displayed at the end. And for tasks
210+
* taking more than 5 seconds to complete, the total time is displayed upon
211+
* completion.
212+
*
182213
* Every 2.5% of progress another number or period is emitted. Note that
183214
* GDALTermProgress() uses internal static data to keep track of the last
184215
* percentage reported and will get confused if two terminal based progress
@@ -200,30 +231,112 @@ int CPL_STDCALL GDALTermProgress(double dfComplete,
200231
CPL_UNUSED const char *pszMessage,
201232
CPL_UNUSED void *pProgressArg)
202233
{
203-
const int nThisTick =
204-
std::min(40, std::max(0, static_cast<int>(dfComplete * 40.0)));
234+
constexpr int MAX_TICKS = 40;
235+
constexpr int MAJOR_TICK_SPACING = 4;
236+
constexpr int LENGTH_OF_0_TO_100_PROGRESS =
237+
GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);
238+
239+
const int nThisTick = std::min(
240+
MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));
205241

206242
// Have we started a new progress run?
207243
static int nLastTick = -1;
208-
if (nThisTick < nLastTick && nLastTick >= 39)
244+
static time_t nStartTime = 0;
245+
// whether estimated remaining time is displayed
246+
static bool bETADisplayed = false;
247+
// number of characters displayed during last progress call
248+
static int nCharacterCountLastTime = 0;
249+
// maximum number of characters displayed during previous calls
250+
static int nCharacterCountMax = 0;
251+
if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
252+
{
253+
bETADisplayed = false;
209254
nLastTick = -1;
255+
nCharacterCountLastTime = 0;
256+
nCharacterCountMax = 0;
257+
}
210258

211259
if (nThisTick <= nLastTick)
212260
return TRUE;
213261

262+
const time_t nCurTime = time(nullptr);
263+
if (nLastTick < 0)
264+
nStartTime = nCurTime;
265+
266+
constexpr int MIN_DELAY_FOR_ETA = 5; // in seconds
267+
if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
268+
dfComplete < 0.5)
269+
{
270+
static bool bIsTTY = CPLIsInteractive(stdout);
271+
bETADisplayed = bIsTTY;
272+
}
273+
if (bETADisplayed)
274+
{
275+
for (int i = 0; i < nCharacterCountLastTime; ++i)
276+
fprintf(stdout, "\b");
277+
nLastTick = -1;
278+
nCharacterCountLastTime = 0;
279+
}
280+
214281
while (nThisTick > nLastTick)
215282
{
216283
++nLastTick;
217-
if (nLastTick % 4 == 0)
218-
fprintf(stdout, "%d", (nLastTick / 4) * 10);
284+
if (nLastTick % MAJOR_TICK_SPACING == 0)
285+
{
286+
const int nPercent = (nLastTick * 100) / MAX_TICKS;
287+
nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
288+
}
219289
else
220-
fprintf(stdout, ".");
290+
{
291+
nCharacterCountLastTime += fprintf(stdout, ".");
292+
}
221293
}
222294

223-
if (nThisTick == 40)
224-
fprintf(stdout, " - done.\n");
295+
if (nThisTick == MAX_TICKS)
296+
{
297+
nCharacterCountLastTime += fprintf(stdout, " - done");
298+
if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
299+
{
300+
const int nEllapsed = static_cast<int>(nCurTime - nStartTime);
301+
const int nHours = nEllapsed / 3600;
302+
const int nMins = (nEllapsed % 3600) / 60;
303+
const int nSecs = nEllapsed % 60;
304+
nCharacterCountLastTime +=
305+
fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs);
306+
for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
307+
nCharacterCountLastTime += fprintf(stdout, " ");
308+
}
309+
else
310+
{
311+
fprintf(stdout, ".");
312+
}
313+
fprintf(stdout, "\n");
314+
}
225315
else
316+
{
317+
if (bETADisplayed)
318+
{
319+
for (int i = nCharacterCountLastTime;
320+
i < LENGTH_OF_0_TO_100_PROGRESS; ++i)
321+
nCharacterCountLastTime += fprintf(stdout, " ");
322+
323+
const double dfETA =
324+
(nCurTime - nStartTime) * (1.0 / dfComplete - 1);
325+
const int nETA = static_cast<int>(dfETA + 0.5);
326+
const int nHours = nETA / 3600;
327+
const int nMins = (nETA % 3600) / 60;
328+
const int nSecs = nETA % 60;
329+
nCharacterCountLastTime +=
330+
fprintf(stdout, " - estimated remaining time: %02d:%02d:%02d",
331+
nHours, nMins, nSecs);
332+
for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
333+
nCharacterCountLastTime += fprintf(stdout, " ");
334+
}
226335
fflush(stdout);
336+
}
337+
338+
if (nCharacterCountLastTime > nCharacterCountMax)
339+
nCharacterCountMax = nCharacterCountLastTime;
227340

228341
return TRUE;
229342
}

0 commit comments

Comments
 (0)