LCOV - code coverage report
Current view: top level - port - cpl_progress.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 57 91 62.6 %
Date: 2025-01-18 12:42:00 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  CPL - Common Portability Library
       4             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       5             :  * Purpose:  Progress function implementations.
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2013, Frank Warmerdam
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_progress.h"
      14             : 
      15             : #include <cmath>
      16             : #include <cstdio>
      17             : #include <ctime>
      18             : 
      19             : #include <algorithm>
      20             : 
      21             : #include "cpl_conv.h"
      22             : #include "cpl_string.h"
      23             : 
      24             : /************************************************************************/
      25             : /*                         GDALDummyProgress()                          */
      26             : /************************************************************************/
      27             : 
      28             : /**
      29             :  * \brief Stub progress function.
      30             :  *
      31             :  * This is a stub (does nothing) implementation of the GDALProgressFunc()
      32             :  * semantics.  It is primarily useful for passing to functions that take
      33             :  * a GDALProgressFunc() argument but for which the application does not want
      34             :  * to use one of the other progress functions that actually do something.
      35             :  */
      36             : 
      37      454740 : int CPL_STDCALL GDALDummyProgress(double /* dfComplete */,
      38             :                                   const char * /* pszMessage */,
      39             :                                   void * /* pData */)
      40             : {
      41      454740 :     return TRUE;
      42             : }
      43             : 
      44             : /************************************************************************/
      45             : /*                         GDALScaledProgress()                         */
      46             : /************************************************************************/
      47             : typedef struct
      48             : {
      49             :     GDALProgressFunc pfnProgress;
      50             :     void *pData;
      51             :     double dfMin;
      52             :     double dfMax;
      53             : } GDALScaledProgressInfo;
      54             : 
      55             : /**
      56             :  * \brief Scaled progress transformer.
      57             :  *
      58             :  * This is the progress function that should be passed along with the
      59             :  * callback data returned by GDALCreateScaledProgress().
      60             :  */
      61             : 
      62      833541 : int CPL_STDCALL GDALScaledProgress(double dfComplete, const char *pszMessage,
      63             :                                    void *pData)
      64             : 
      65             : {
      66      833541 :     GDALScaledProgressInfo *psInfo =
      67             :         reinterpret_cast<GDALScaledProgressInfo *>(pData);
      68             : 
      69             :     // Optimization if GDALCreateScaledProgress() provided with
      70             :     // GDALDummyProgress.
      71      833541 :     if (psInfo == nullptr)
      72      476896 :         return TRUE;
      73             : 
      74      713290 :     return psInfo->pfnProgress(dfComplete * (psInfo->dfMax - psInfo->dfMin) +
      75      356645 :                                    psInfo->dfMin,
      76      356645 :                                pszMessage, psInfo->pData);
      77             : }
      78             : 
      79             : /************************************************************************/
      80             : /*                      GDALCreateScaledProgress()                      */
      81             : /************************************************************************/
      82             : 
      83             : /**
      84             :  * \brief Create scaled progress transformer.
      85             :  *
      86             :  * Sometimes when an operations wants to report progress it actually
      87             :  * invokes several subprocesses which also take GDALProgressFunc()s,
      88             :  * and it is desirable to map the progress of each sub operation into
      89             :  * a portion of 0.0 to 1.0 progress of the overall process.  The scaled
      90             :  * progress function can be used for this.
      91             :  *
      92             :  * For each subsection a scaled progress function is created and
      93             :  * instead of passing the overall progress func down to the sub functions,
      94             :  * the GDALScaledProgress() function is passed instead.
      95             :  *
      96             :  * @param dfMin the value to which 0.0 in the sub operation is mapped.
      97             :  * @param dfMax the value to which 1.0 is the sub operation is mapped.
      98             :  * @param pfnProgress the overall progress function.
      99             :  * @param pData the overall progress function callback data.
     100             :  *
     101             :  * @return pointer to pass as pProgressArg to sub functions.  Should be freed
     102             :  * with GDALDestroyScaledProgress().
     103             :  *
     104             :  * Example:
     105             :  *
     106             :  * \code
     107             :  *   int MyOperation( ..., GDALProgressFunc pfnProgress, void *pProgressData );
     108             :  *
     109             :  *   {
     110             :  *       void *pScaledProgress;
     111             :  *
     112             :  *       pScaledProgress = GDALCreateScaledProgress( 0.0, 0.5, pfnProgress,
     113             :  *                                                   pProgressData );
     114             :  *       GDALDoLongSlowOperation( ..., GDALScaledProgress, pScaledProgress );
     115             :  *       GDALDestroyScaledProgress( pScaledProgress );
     116             :  *
     117             :  *       pScaledProgress = GDALCreateScaledProgress( 0.5, 1.0, pfnProgress,
     118             :  *                                                   pProgressData );
     119             :  *       GDALDoAnotherOperation( ..., GDALScaledProgress, pScaledProgress );
     120             :  *       GDALDestroyScaledProgress( pScaledProgress );
     121             :  *
     122             :  *       return ...;
     123             :  *   }
     124             :  * \endcode
     125             :  */
     126             : 
     127      509589 : void *CPL_STDCALL GDALCreateScaledProgress(double dfMin, double dfMax,
     128             :                                            GDALProgressFunc pfnProgress,
     129             :                                            void *pData)
     130             : 
     131             : {
     132      509589 :     if (pfnProgress == nullptr || pfnProgress == GDALDummyProgress)
     133      499326 :         return nullptr;
     134             : 
     135             :     GDALScaledProgressInfo *psInfo = static_cast<GDALScaledProgressInfo *>(
     136       10263 :         CPLCalloc(sizeof(GDALScaledProgressInfo), 1));
     137             : 
     138       10263 :     if (std::abs(dfMin - dfMax) < 0.0000001)
     139         171 :         dfMax = dfMin + 0.01;
     140             : 
     141       10263 :     psInfo->pData = pData;
     142       10263 :     psInfo->pfnProgress = pfnProgress;
     143       10263 :     psInfo->dfMin = dfMin;
     144       10263 :     psInfo->dfMax = dfMax;
     145             : 
     146       10263 :     return static_cast<void *>(psInfo);
     147             : }
     148             : 
     149             : /************************************************************************/
     150             : /*                     GDALDestroyScaledProgress()                      */
     151             : /************************************************************************/
     152             : 
     153             : /**
     154             :  * \brief Cleanup scaled progress handle.
     155             :  *
     156             :  * This function cleans up the data associated with a scaled progress function
     157             :  * as returned by GADLCreateScaledProgress().
     158             :  *
     159             :  * @param pData scaled progress handle returned by GDALCreateScaledProgress().
     160             :  */
     161             : 
     162      509515 : void CPL_STDCALL GDALDestroyScaledProgress(void *pData)
     163             : 
     164             : {
     165      509515 :     CPLFree(pData);
     166      509510 : }
     167             : 
     168             : /************************************************************************/
     169             : /*                      GDALTermProgressWidth()                         */
     170             : /************************************************************************/
     171             : 
     172             : static constexpr int GDALTermProgressWidth(int nMaxTicks, int nMajorTickSpacing)
     173             : {
     174             :     int nWidth = 0;
     175             :     for (int i = 0; i <= nMaxTicks; i++)
     176             :     {
     177             :         if (i % nMajorTickSpacing == 0)
     178             :         {
     179             :             int nPercent = (i * 100) / nMaxTicks;
     180             :             do
     181             :             {
     182             :                 nWidth++;
     183             :             } while (nPercent /= 10);
     184             :         }
     185             :         else
     186             :         {
     187             :             nWidth += 1;
     188             :         }
     189             :     }
     190             :     return nWidth;
     191             : }
     192             : 
     193             : /************************************************************************/
     194             : /*                          GDALTermProgress()                          */
     195             : /************************************************************************/
     196             : 
     197             : /**
     198             :  * \fn GDALTermProgress(double, const char*, void*)
     199             :  * \brief Simple progress report to terminal.
     200             :  *
     201             :  * This progress reporter prints simple progress report to the
     202             :  * terminal window.  The progress report generally looks something like
     203             :  * this:
     204             : 
     205             : \verbatim
     206             : 0...10...20...30...40...50...60...70...80...90...100 - done.
     207             : \endverbatim
     208             : 
     209             :  * Starting with GDAL 3.11, for tasks estimated to take more than 10 seconds,
     210             :  * an estimated remaining time is also displayed at the end. And for tasks
     211             :  * taking more than 5 seconds to complete, the total time is displayed upon
     212             :  * completion.
     213             :  *
     214             :  * Every 2.5% of progress another number or period is emitted.  Note that
     215             :  * GDALTermProgress() uses internal static data to keep track of the last
     216             :  * percentage reported and will get confused if two terminal based progress
     217             :  * reportings are active at the same time.
     218             :  *
     219             :  * The GDALTermProgress() function maintains an internal memory of the
     220             :  * last percentage complete reported in a static variable, and this makes
     221             :  * it unsuitable to have multiple GDALTermProgress()'s active either in a
     222             :  * single thread or across multiple threads.
     223             :  *
     224             :  * @param dfComplete completion ratio from 0.0 to 1.0.
     225             :  * @param pszMessage optional message.
     226             :  * @param pProgressArg ignored callback data argument.
     227             :  *
     228             :  * @return Always returns TRUE indicating the process should continue.
     229             :  */
     230             : 
     231      112111 : int CPL_STDCALL GDALTermProgress(double dfComplete,
     232             :                                  CPL_UNUSED const char *pszMessage,
     233             :                                  CPL_UNUSED void *pProgressArg)
     234             : {
     235      112111 :     constexpr int MAX_TICKS = 40;
     236      112111 :     constexpr int MAJOR_TICK_SPACING = 4;
     237      112111 :     constexpr int LENGTH_OF_0_TO_100_PROGRESS =
     238             :         GDALTermProgressWidth(MAX_TICKS, MAJOR_TICK_SPACING);
     239             : 
     240             :     const int nThisTick = std::min(
     241      112111 :         MAX_TICKS, std::max(0, static_cast<int>(dfComplete * MAX_TICKS)));
     242             : 
     243             :     // Have we started a new progress run?
     244             :     static int nLastTick = -1;
     245             :     static time_t nStartTime = 0;
     246             :     // whether estimated remaining time is displayed
     247             :     static bool bETADisplayed = false;
     248             :     // number of characters displayed during last progress call
     249             :     static int nCharacterCountLastTime = 0;
     250             :     // maximum number of characters displayed during previous calls
     251             :     static int nCharacterCountMax = 0;
     252      112111 :     if (nThisTick < nLastTick && nLastTick >= MAX_TICKS - 1)
     253             :     {
     254           9 :         bETADisplayed = false;
     255           9 :         nLastTick = -1;
     256           9 :         nCharacterCountLastTime = 0;
     257           9 :         nCharacterCountMax = 0;
     258             :     }
     259             : 
     260      112111 :     if (nThisTick <= nLastTick)
     261      104740 :         return TRUE;
     262             : 
     263        7371 :     const time_t nCurTime = time(nullptr);
     264        7371 :     if (nLastTick < 0)
     265         436 :         nStartTime = nCurTime;
     266             : 
     267        7371 :     constexpr int MIN_DELAY_FOR_ETA = 5;  // in seconds
     268        7371 :     if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA && dfComplete > 0 &&
     269             :         dfComplete < 0.5)
     270             :     {
     271           0 :         static bool bIsTTY = CPLIsInteractive(stdout);
     272           0 :         bETADisplayed = bIsTTY;
     273             :     }
     274        7371 :     if (bETADisplayed)
     275             :     {
     276           0 :         for (int i = 0; i < nCharacterCountLastTime; ++i)
     277           0 :             fprintf(stdout, "\b");
     278           0 :         nLastTick = -1;
     279           0 :         nCharacterCountLastTime = 0;
     280             : 
     281             : #ifdef _WIN32
     282             :         constexpr const char *WINDOWS_TERMINAL_ENV_VAR = "WT_SESSION";
     283             :         constexpr const char *CONEMU_ENV_VAR = "ConEmuANSI";
     284             : #endif
     285           0 :         static const bool bAllowOSC94 = CPLTestBool(CPLGetConfigOption(
     286             :             "GDAL_TERM_PROGRESS_OSC_9_4",
     287             : #ifdef _WIN32
     288             :             // Detect if we are running under Windows Terminal
     289             :             (CPLGetConfigOption(WINDOWS_TERMINAL_ENV_VAR, nullptr) != nullptr ||
     290             :              // or ConEmu
     291             :              CPLGetConfigOption(CONEMU_ENV_VAR, nullptr) != nullptr)
     292             :                 ? "YES"
     293             :                 : "NO"
     294             : #else
     295             :             "YES"
     296             : #endif
     297           0 :             ));
     298           0 :         if (bAllowOSC94)
     299             :         {
     300             :             // Implement OSC 9;4 progress reporting protocol
     301             :             // https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC
     302           0 :             if (nThisTick == MAX_TICKS)
     303           0 :                 fprintf(stdout, "\x1b]9;4;0;100\x07");
     304             :             else
     305           0 :                 fprintf(stdout, "\x1b]9;4;1;%d\x07",
     306           0 :                         (nThisTick * 100) / MAX_TICKS);
     307             :         }
     308             :     }
     309             : 
     310       23255 :     while (nThisTick > nLastTick)
     311             :     {
     312       15884 :         ++nLastTick;
     313       15884 :         if (nLastTick % MAJOR_TICK_SPACING == 0)
     314             :         {
     315        4298 :             const int nPercent = (nLastTick * 100) / MAX_TICKS;
     316        4298 :             nCharacterCountLastTime += fprintf(stdout, "%d", nPercent);
     317             :         }
     318             :         else
     319             :         {
     320       11586 :             nCharacterCountLastTime += fprintf(stdout, ".");
     321             :         }
     322             :     }
     323             : 
     324        7371 :     if (nThisTick == MAX_TICKS)
     325             :     {
     326         386 :         nCharacterCountLastTime += fprintf(stdout, " - done");
     327         386 :         if (nCurTime - nStartTime >= MIN_DELAY_FOR_ETA)
     328             :         {
     329           0 :             const int nElapsed = static_cast<int>(nCurTime - nStartTime);
     330           0 :             const int nHours = nElapsed / 3600;
     331           0 :             const int nMins = (nElapsed % 3600) / 60;
     332           0 :             const int nSecs = nElapsed % 60;
     333           0 :             nCharacterCountLastTime +=
     334           0 :                 fprintf(stdout, " in %02d:%02d:%02d.", nHours, nMins, nSecs);
     335           0 :             for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
     336           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     337             :         }
     338             :         else
     339             :         {
     340         386 :             fprintf(stdout, ".");
     341             :         }
     342         386 :         fprintf(stdout, "\n");
     343             :     }
     344             :     else
     345             :     {
     346        6985 :         if (bETADisplayed)
     347             :         {
     348           0 :             for (int i = nCharacterCountLastTime;
     349           0 :                  i < LENGTH_OF_0_TO_100_PROGRESS; ++i)
     350           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     351             : 
     352           0 :             const double dfETA =
     353           0 :                 (nCurTime - nStartTime) * (1.0 / dfComplete - 1);
     354           0 :             const int nETA = static_cast<int>(dfETA + 0.5);
     355           0 :             const int nHours = nETA / 3600;
     356           0 :             const int nMins = (nETA % 3600) / 60;
     357           0 :             const int nSecs = nETA % 60;
     358           0 :             nCharacterCountLastTime +=
     359           0 :                 fprintf(stdout, " - estimated remaining time: %02d:%02d:%02d",
     360             :                         nHours, nMins, nSecs);
     361           0 :             for (int i = nCharacterCountLastTime; i < nCharacterCountMax; ++i)
     362           0 :                 nCharacterCountLastTime += fprintf(stdout, " ");
     363             :         }
     364        6985 :         fflush(stdout);
     365             :     }
     366             : 
     367        7371 :     if (nCharacterCountLastTime > nCharacterCountMax)
     368        7371 :         nCharacterCountMax = nCharacterCountLastTime;
     369             : 
     370        7371 :     return TRUE;
     371             : }

Generated by: LCOV version 1.14