From 213262fa53b7d5ca8d8432b7a30e0d9322a8aad6 Mon Sep 17 00:00:00 2001 From: drh <> Date: Sun, 1 Feb 2026 15:58:37 +0000 Subject: [PATCH] Enhance the ".timer" command in the CLI to accept the "once" argument, and so that it leaves its last real-time result in the $TIMER variable. Also fix a harmless warning from an earlier check-in. FossilOrigin-Name: 8ad7dffc261cbb3f7c9b4d38b04282c805c1c047fb431076f85e577c81f21574 --- manifest | 18 +-- manifest.uuid | 2 +- src/select.c | 2 +- src/shell.c.in | 320 ++++++++++++++++++++++++----------------------- test/shell1.test | 4 +- test/shell5.test | 1 + 6 files changed, 180 insertions(+), 167 deletions(-) diff --git a/manifest b/manifest index 4306bb47d0..a129b52173 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\sa\smissed\soptimization\sopportunity\sdue\sto\sa\stypo\sin\scheck-in\s[898bfa1afd8260ea].\nReported\sby\s[forum:/forumpost/2026-01-31T00:49:53z|forum\spost\s2026-01-31T00:49:53z]. -D 2026-01-31T02:17:55.945 +C Enhance\sthe\s".timer"\scommand\sin\sthe\sCLI\sto\saccept\sthe\s"once"\sargument,\sand\nso\sthat\sit\sleaves\sits\slast\sreal-time\sresult\sin\sthe\s$TIMER\svariable.\s\sAlso\nfix\sa\sharmless\swarning\sfrom\san\searlier\scheck-in. +D 2026-02-01T15:58:37.050 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -738,8 +738,8 @@ F src/printf.c b1b29b5e58e1530d5daeee5963d3c318d8ab2d7e38437580e28755753e0c1ded F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 47aa7fdc9ec4c19b103ac5e79d7887d30119b5675309facf5eed1118391c868b F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 -F src/select.c 4bee1bb231771e7c6e5aef243b1f74c3d330df5a005909d5e2c338fb1510fe55 -F src/shell.c.in c5c6cbbc518472ebd9662c9876133517f5e85f995e9725fafaeda88fc55b6ac0 +F src/select.c 098caf322d17cd480a5ebd81b9252b9ad90c72be7d5cdc5875e4a0a43e0ba965 +F src/shell.c.in 4dfd1ff15cdfac8778a5dcd6c0f3db1789d0919e79c9725cc7255d1d9bb1e273 F src/sqlite.h.in 8bcbaecfe2cbecf8c5c1381354fcdd7d307443e88b4953fccb222456c1267b61 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 1b7a0ee438bb5c2896d0609c537e917d8057b3340f6ad004d2de44f03e3d3cca @@ -1618,11 +1618,11 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 56f200ba4b36cbe8229d84d848e6a8d69eb91a75b2deaf28c454027433c6ea95 +F test/shell1.test 5055a13bde31e92590311c91777d6b646082dc13250de8c07dc0db1efbdd864d F test/shell2.test dc541d2681503e55466a24d35a4cbf8ca5b90b8fcdef37fc4db07373a67d31d3 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e F test/shell4.test e25580a792b7b54560c3a76b6968bd8189261f38979fe28e6bc6312c5db280db -F test/shell5.test c5e8fb055ca74511eb0e757b568e48853d99306aea4a7e6a47a61dcf492a7d3c +F test/shell5.test a9cd2c8b62e125049ef500937674f47dd6787f0157ac0515aa554044a4dc3ea9 F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bdbb8 F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871 @@ -2194,8 +2194,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P d294106ecb78d765305ab740007f4a678e28baa13ae403fe57ea9cbfc259620f -R 464b9c781876348fa88209b9bcbdb4fe +P 3a4f9a323da90611d7eda51b90cb058175ddde0a128e1ff00ce58cc83af0f376 +R af9796fbe8093a32969c1859761a2b45 U drh -Z fade084440f5178d675ec00dcd7ba976 +Z 7d607b0d09ce1651ab8b2dc2f7cb7118 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b6fb2c3daa..672fb6d74d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3a4f9a323da90611d7eda51b90cb058175ddde0a128e1ff00ce58cc83af0f376 +8ad7dffc261cbb3f7c9b4d38b04282c805c1c047fb431076f85e577c81f21574 diff --git a/src/select.c b/src/select.c index 98f4047da0..73dc8655ca 100644 --- a/src/select.c +++ b/src/select.c @@ -3497,7 +3497,7 @@ static int multiSelectByMerge( assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; - if( aPermute[i]!=i-1 ) bKeep = 1; + if( aPermute[i]!=(u32)i-1 ) bKeep = 1; } if( bKeep==0 ){ sqlite3DbFreeNN(db, aPermute); diff --git a/src/shell.c.in b/src/shell.c.in index b6acc86d29..7d689cf1a5 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -306,9 +306,6 @@ static void cli_exit(int rc){ #define eputz(z) cli_puts(z,stderr) #define sputz(fp,z) cli_puts(z,fp) -/* True if the timer is enabled */ -static int enableTimer = 0; - /* A version of strcmp() that works with NULL values */ static int cli_strcmp(const char *a, const char *b){ if( a==0 ) a = ""; @@ -353,150 +350,6 @@ static sqlite3_int64 timeOfDay(void){ #endif } -#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) -#include -#include - -/* VxWorks does not support getrusage() as far as we can determine */ -#if defined(_WRS_KERNEL) || defined(__RTP__) -struct rusage { - struct timeval ru_utime; /* user CPU time used */ - struct timeval ru_stime; /* system CPU time used */ -}; -#define getrusage(A,B) memset(B,0,sizeof(*B)) -#endif - - -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; /* CPU time at start */ -static sqlite3_int64 iBegin; /* Wall-clock time at start */ - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( enableTimer ){ - getrusage(RUSAGE_SELF, &sBegin); - iBegin = timeOfDay(); - } -} - -/* Return the difference of two time_structs in seconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + - (double)(pEnd->tv_sec - pStart->tv_sec); -} - -/* -** Print the timing results. -*/ -static void endTimer(FILE *out){ - if( enableTimer ){ - sqlite3_int64 iEnd = timeOfDay(); - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - cli_printf(out, "Run Time: real %.6f user %.6f sys %.6f\n", - (iEnd - iBegin)*0.000001, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER(X) endTimer(X) -#define HAS_TIMER 1 - -#elif (defined(_WIN32) || defined(WIN32)) - -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -static sqlite3_int64 ftWallBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, - LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; - -/* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). -*/ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { - /* GetProcessTimes() isn't supported in WIN95 and some other Windows - ** versions. See if the version we are running on has it, and if it - ** does, save off a pointer to it and the current process handle. - */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = - (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); - } - } - } - return 0; -} - -/* -** Begin timing an operation -*/ -static void beginTimer(void){ - if( enableTimer && getProcessTimesAddr ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess,&ftCreation,&ftExit, - &ftKernelBegin,&ftUserBegin); - ftWallBegin = timeOfDay(); - } -} - -/* Return the difference of two FILETIME structs in seconds */ -static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ - sqlite_int64 i64Start = *((sqlite_int64 *) pStart); - sqlite_int64 i64End = *((sqlite_int64 *) pEnd); - return (double) ((i64End - i64Start) / 10000000.0); -} - -/* -** Print the timing results. -*/ -static void endTimer(FILE *out){ - if( enableTimer && getProcessTimesAddr){ - FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; - sqlite3_int64 ftWallEnd = timeOfDay(); - getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); -#ifdef _WIN64 - /* microsecond precision on 64-bit windows */ - cli_printf(out, "Run Time: real %.6f user %f sys %f\n", - (ftWallEnd - ftWallBegin)*0.000001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#else - /* millisecond precisino on 32-bit windows */ - cli_printf(out, "Run Time: real %.3f user %.3f sys %.3f\n", - (ftWallEnd - ftWallBegin)*0.000001, - timeDiff(&ftUserBegin, &ftUserEnd), - timeDiff(&ftKernelBegin, &ftKernelEnd)); -#endif - } -} - -#define BEGIN_TIMER beginTimer() -#define END_TIMER(X) endTimer(X) -#define HAS_TIMER hasTimer() - -#else -#define BEGIN_TIMER -#define END_TIMER(X) /*no-op*/ -#define HAS_TIMER 0 -#endif /* ** Used to prevent warnings about unused parameters @@ -1273,7 +1126,9 @@ struct ShellState { unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ u8 nPopOutput; /* Revert .output settings when reaching zero */ u8 nPopMode; /* Revert .mode settings when reaching zero */ + u8 enableTimer; /* Enable the timer. 2: permanently 1: only once */ int inputNesting; /* Track nesting level of .read and other redirects */ + double prevTimer; /* Last reported timer value */ i64 lineno; /* Line number of last line read from in */ const char *zInFile; /* Name of the input file */ int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ @@ -1530,6 +1385,154 @@ static const ModeInfo aModeInfo[] = { */ #define MAX_INPUT_NESTING 25 +/************************* BEGIN PERFORMANCE TIMER *****************************/ +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include +#include +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + +/* +** Begin timing an operation +*/ +static void beginTimer(ShellState *p){ + if( p->enableTimer ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); +} + +/* +** Print the timing results. +*/ +static void endTimer(ShellState *p){ + if( p->enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + p->prevTimer = (iEnd - iBegin)*0.000001; + cli_printf(p->out, "Run Time: real %.6f user %.6f sys %.6f\n", + p->prevTimer, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + if( p->enableTimer==1 ) p->enableTimer = 0; + } +} + +#define BEGIN_TIMER(X) beginTimer(X) +#define END_TIMER(X) endTimer(X) +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + +/* +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). +*/ +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } + } + return 0; +} + +/* +** Begin timing an operation +*/ +static void beginTimer(ShellState *p){ + if( p->enableTimer && getProcessTimesAddr ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); + } +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +/* +** Print the timing results. +*/ +static void endTimer(ShellState *p){ + if( p->enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + p->prevTimer = (ftWallEnd - ftWallBegin)*0.000001; +#ifdef _WIN64 + /* microsecond precision on 64-bit windows */ + cli_printf(p->out, "Run Time: real %.6f user %f sys %f\n", + p->prevTimer, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#else + /* millisecond precisino on 32-bit windows */ + cli_printf(p->out, "Run Time: real %.3f user %.3f sys %.3f\n", + p->prevTimer, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); +#endif + if( p->enableTimer==1 ) p->enableTimer = 0; + } +} + +#define BEGIN_TIMER(X) beginTimer(X) +#define END_TIMER(X) endTimer(X) +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER(X) /* no-op */ +#define END_TIMER(X) /*no-op*/ +#define HAS_TIMER 0 +#endif +/************************* END PERFORMANCE TIMER ******************************/ /* ** Clear a display mode, freeing any allocated memory that it @@ -2992,6 +2995,8 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){ memcpy(zBuf, &zVar[6], szVar-5); sqlite3_bind_text64(pStmt, i, zBuf, szVar-6, sqlite3_free, SQLITE_UTF8); } + }else if( strcmp(zVar, "$TIMER")==0 ){ + sqlite3_bind_double(pStmt, i, pArg->prevTimer); #ifdef SQLITE_ENABLE_CARRAY }else if( strncmp(zVar, "$carray_", 8)==0 ){ static char *azColorNames[] = { @@ -3253,6 +3258,8 @@ static int shell_exec( isExplain = sqlite3_stmt_isexplain(pStmt); if( pArg && pArg->mode.autoEQP && isExplain==0 && pArg->dot.nArg==0 ){ int triggerEQP = 0; + u8 savedEnableTimer = pArg->enableTimer; + pArg->enableTimer = 0; disable_debug_trace_modes(); sqlite3_db_config(db, SQLITE_DBCONFIG_TRIGGER_EQP, -1, &triggerEQP); if( pArg->mode.autoEQP>=AUTOEQP_trigger ){ @@ -3274,6 +3281,7 @@ static int shell_exec( sqlite3_reset(pStmt); sqlite3_stmt_explain(pStmt, 0); restore_debug_trace_modes(); + pArg->enableTimer = savedEnableTimer; } bind_prepared_stmt(pArg, pStmt); @@ -3879,7 +3887,7 @@ static const char *(azHelp[]) = { ",testctrl CMD ... Run various sqlite3_test_control() operations", " Run \".testctrl\" with no arguments for details", ".timeout MS Try opening locked tables for MS milliseconds", - ".timer on|off Turn SQL timer on or off", + ".timer on|off|once Turn SQL timer on or off.", #ifndef SQLITE_OMIT_TRACE ".trace ?OPTIONS? Output each SQL statement as it is run", " FILE Send output to FILE", @@ -11726,13 +11734,17 @@ static int do_meta_command(const char *zLine, ShellState *p){ if( c=='t' && n>=5 && cli_strncmp(azArg[0], "timer", n)==0 ){ if( nArg==2 ){ - enableTimer = booleanValue(azArg[1]); - if( enableTimer && !HAS_TIMER ){ + if( cli_strcmp(azArg[1],"once")==0 ){ + p->enableTimer = 1; + }else{ + p->enableTimer = 2*booleanValue(azArg[1]); + } + if( p->enableTimer && !HAS_TIMER ){ eputz("Error: timer not available on this system.\n"); - enableTimer = 0; + p->enableTimer = 0; } }else{ - eputz("Usage: .timer on|off\n"); + eputz("Usage: .timer on|off|once\n"); rc = 1; } }else @@ -12144,9 +12156,9 @@ static int runOneSqlLine(ShellState *p, char *zSql, FILE *in, int startline){ open_db(p, 0); if( ShellHasFlag(p,SHFLG_Backslash) ) resolve_backslashes(zSql); if( p->flgProgress & SHELL_PROGRESS_RESET ) p->nProgress = 0; - BEGIN_TIMER; + BEGIN_TIMER(p); rc = shell_exec(p, zSql, &zErrMsg); - END_TIMER(p->out); + END_TIMER(p); if( rc || zErrMsg ){ char zPrefix[100]; const char *zErrorTail; diff --git a/test/shell1.test b/test/shell1.test index 1900055c43..84b43c5d50 100644 --- a/test/shell1.test +++ b/test/shell1.test @@ -752,7 +752,7 @@ do_test shell1-3.26.6 { # .timer ON|OFF Turn the CPU timer measurement on or off do_test shell1-3.27.1 { catchcmd "test.db" ".timer" -} {1 {Usage: .timer on|off}} +} {1 {Usage: .timer on|off|once}} do_test shell1-3.27.2 { catchcmd "test.db" ".timer ON" } {0 {}} @@ -762,7 +762,7 @@ do_test shell1-3.27.3 { do_test shell1-3.27.4 { # too many arguments catchcmd "test.db" ".timer OFF BAD" -} {1 {Usage: .timer on|off}} +} {1 {Usage: .timer on|off|once}} do_test shell1-3.28.1 { catchcmd test.db \ diff --git a/test/shell5.test b/test/shell5.test index b21b24b924..559dc3ce76 100644 --- a/test/shell5.test +++ b/test/shell5.test @@ -346,6 +346,7 @@ do_test shell5-1.10.1 { puts $out {column1,column2,column3,column4} puts $out "x1,x2%\"x3,\"x3\\\"data\\\"3\",x4" close $out + db close forcedelete test.db catchcmd test.db { CREATE TABLE t1(a,b,c,d); -- 2.47.3