Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -668,12 +668,12 @@ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ int (*xBusyHandler)(void*); /* Function to call when busy */ void *pBusyHandlerArg; /* Context argument for xBusyHandler */ + int nHit, nMiss; /* Total cache hits and misses */ #ifdef SQLITE_TEST - int nHit, nMiss; /* Cache hits and missing */ int nRead, nWrite; /* Database pages read/written */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ #ifdef SQLITE_HAS_CODEC void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ @@ -4167,11 +4167,11 @@ ** The doNotSpill flag inhibits all cache spilling regardless of whether ** or not a sync is required. This is set during a rollback. ** ** Spilling is also prohibited when in an error state since that could ** lead to database corruption. In the current implementaton it - ** is impossible for sqlite3PCacheFetch() to be called with createFlag==1 + ** is impossible for sqlite3PcacheFetch() to be called with createFlag==1 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() ** test for the error state as a safeguard against future changes. */ if( NEVER(pPager->errCode) ) return SQLITE_OK; @@ -5003,18 +5003,17 @@ if( (*ppPage)->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ assert( pgno<=PAGER_MAX_PGNO && pgno!=PAGER_MJ_PGNO(pPager) ); - PAGER_INCR(pPager->nHit); + pPager->nHit++; return SQLITE_OK; }else{ /* The pager cache has created a new page. Its content needs to ** be initialized. */ - PAGER_INCR(pPager->nMiss); pPg = *ppPage; pPg->pPager = pPager; /* The maximum page number is 2^31. Return SQLITE_CORRUPT if a page ** number greater than this, or the unused locking-page, is requested. */ @@ -5046,10 +5045,11 @@ } memset(pPg->pData, 0, pPager->pageSize); IOTRACE(("ZERO %p %d\n", pPager, pgno)); }else{ assert( pPg->pPager==pPager ); + pPager->nMiss++; rc = readDbPage(pPg); if( rc!=SQLITE_OK ){ goto pager_acquire_err; } } @@ -6079,10 +6079,21 @@ a[9] = pPager->nRead; a[10] = pPager->nWrite; return a; } #endif + +/* +** This function is used to access the cache hit/miss counts maintained +** by the Pager object. Before returning, *pnHit is incremented by the +** total number of cache-hits that have occurred since the pager was +** created, and *pnMiss is incremented by the total number of misses. +*/ +void sqlite3PagerCacheStats(Pager *pPager, int *pnHit, int *pnMiss){ + *pnHit += pPager->nHit; + *pnMiss += pPager->nMiss; +} /* ** Return true if this is an in-memory pager. */ int sqlite3PagerIsMemdb(Pager *pPager){ Index: src/pager.h ================================================================== --- src/pager.h +++ src/pager.h @@ -153,10 +153,11 @@ sqlite3_file *sqlite3PagerFile(Pager*); const char *sqlite3PagerJournalname(Pager*); int sqlite3PagerNosync(Pager*); void *sqlite3PagerTempSpace(Pager*); int sqlite3PagerIsMemdb(Pager*); +void sqlite3PagerCacheStats(Pager *, int *, int *); /* Functions used to truncate the database file. */ void sqlite3PagerTruncateImage(Pager*,Pgno); #if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -5808,20 +5808,34 @@ **
This parameter returns the approximate number of of bytes of heap ** and lookaside memory used by all prepared statements associated with ** the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0. **
+** +** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
SQLITE_DBSTATUS_CACHE_HIT
+**
This parameter returns the number of pager cache hits that have +** occurred. ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** is always 0. +**
+** +** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
SQLITE_DBSTATUS_CACHE_MISS
+**
This parameter returns the number of pager cache misses that have +** occurred. ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** is always 0. +**
** */ #define SQLITE_DBSTATUS_LOOKASIDE_USED 0 #define SQLITE_DBSTATUS_CACHE_USED 1 #define SQLITE_DBSTATUS_SCHEMA_USED 2 #define SQLITE_DBSTATUS_STMT_USED 3 #define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 #define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_MAX 6 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_MAX 8 /* Largest defined DBSTATUS */ /* ** CAPI3REF: Prepared Statement Status ** @@ -5872,15 +5886,25 @@ ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to ** improvement performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run. ** +** [[SQLITE_STMTSTATUS_CACHE_HIT]]
SQLITE_STMTSTATUS_CACHE_HIT
+**
^This is the number of pager cache hits encountered during execution of +** the statement.
+** +** [[SQLITE_STMTSTATUS_CACHE_MISS]]
SQLITE_STMTSTATUS_CACHE_MISS
+**
^This is the number of pager cache misses encountered during execution +** of the statement.
+** ** */ #define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 #define SQLITE_STMTSTATUS_SORT 2 #define SQLITE_STMTSTATUS_AUTOINDEX 3 +#define SQLITE_STMTSTATUS_CACHE_HIT 4 +#define SQLITE_STMTSTATUS_CACHE_MISS 5 /* ** CAPI3REF: Custom Page Cache Object ** ** The sqlite3_pcache type is opaque. It is implemented by Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -891,10 +891,11 @@ int nSavepoint; /* Number of non-transaction savepoints */ int nStatement; /* Number of nested statement-transactions */ u8 isTransactionSavepoint; /* True if the outermost savepoint is a TS */ i64 nDeferredCons; /* Net deferred constraints this transaction. */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ + int aHitMiss[2]; /* DBSTATUS_CACHEHIT and CACHEMISS stats */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY /* The following variables are all protected by the STATIC_MASTER ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** Index: src/status.c ================================================================== --- src/status.c +++ src/status.c @@ -215,10 +215,26 @@ *pHighwater = 0; *pCurrent = nByte; break; } + + /* + ** Set *pCurrent to the total cache hits or misses encountered by the + ** database connection since the last reset. *pHighwater is always set to + ** zero. + */ + case SQLITE_DBSTATUS_CACHE_HIT: + case SQLITE_DBSTATUS_CACHE_MISS: { + assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); + *pHighwater = 0; + *pCurrent = db->aHitMiss[op-SQLITE_DBSTATUS_CACHE_HIT]; + if( resetFlag ){ + db->aHitMiss[op-SQLITE_DBSTATUS_CACHE_HIT] = 0; + } + break; + } default: { rc = SQLITE_ERROR; } } Index: src/test1.c ================================================================== --- src/test1.c +++ src/test1.c @@ -2248,10 +2248,12 @@ int op; } aOp[] = { { "SQLITE_STMTSTATUS_FULLSCAN_STEP", SQLITE_STMTSTATUS_FULLSCAN_STEP }, { "SQLITE_STMTSTATUS_SORT", SQLITE_STMTSTATUS_SORT }, { "SQLITE_STMTSTATUS_AUTOINDEX", SQLITE_STMTSTATUS_AUTOINDEX }, + { "SQLITE_STMTSTATUS_CACHE_HIT", SQLITE_STMTSTATUS_CACHE_HIT }, + { "SQLITE_STMTSTATUS_CACHE_MISS", SQLITE_STMTSTATUS_CACHE_MISS }, }; if( objc!=4 ){ Tcl_WrongNumArgs(interp, 1, objv, "STMT PARAMETER RESETFLAG"); return TCL_ERROR; } Index: src/test_malloc.c ================================================================== --- src/test_malloc.c +++ src/test_malloc.c @@ -1323,15 +1323,17 @@ { "CACHE_USED", SQLITE_DBSTATUS_CACHE_USED }, { "SCHEMA_USED", SQLITE_DBSTATUS_SCHEMA_USED }, { "STMT_USED", SQLITE_DBSTATUS_STMT_USED }, { "LOOKASIDE_HIT", SQLITE_DBSTATUS_LOOKASIDE_HIT }, { "LOOKASIDE_MISS_SIZE", SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE }, - { "LOOKASIDE_MISS_FULL", SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL } + { "LOOKASIDE_MISS_FULL", SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL }, + { "CACHE_HIT", SQLITE_DBSTATUS_CACHE_HIT }, + { "CACHE_MISS", SQLITE_DBSTATUS_CACHE_MISS } }; Tcl_Obj *pResult; if( objc!=4 ){ - Tcl_WrongNumArgs(interp, 1, objv, "PARAMETER RESETFLAG"); + Tcl_WrongNumArgs(interp, 1, objv, "DB PARAMETER RESETFLAG"); return TCL_ERROR; } if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR; zOpName = Tcl_GetString(objv[2]); if( memcmp(zOpName, "SQLITE_", 7)==0 ) zOpName += 7; Index: src/vdbe.c ================================================================== --- src/vdbe.c +++ src/vdbe.c @@ -516,10 +516,31 @@ p->zErrMsg = sqlite3DbStrDup(db, pVtab->zErrMsg); sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; } +/* +** Call sqlite3PagerCacheStats() on all database pagers used by the VM +** passed as the first argument, incrementing *pnHit and *pnMiss for +** with each call. +*/ +static void vdbeCacheStats(Vdbe *p, int *pnHit, int *pnMiss){ + int i; + yDbMask mask; + sqlite3 *db; + Db *aDb; + int nDb; + if( p->lockMask==0 ) return; /* The common case */ + db = p->db; + aDb = db->aDb; + nDb = db->nDb; + for(i=0, mask=1; ilockMask)!=0 && ALWAYS(aDb[i].pBt!=0) ){ + sqlite3PagerCacheStats(sqlite3BtreePager(aDb[i].pBt), pnHit, pnMiss); + } + } +} /* ** Execute as much of a VDBE program as we can then return. ** ** sqlite3VdbeMakeReady() must be called before this routine in order to @@ -574,14 +595,17 @@ i64 lastRowid = db->lastRowid; /* Saved value of the last insert ROWID */ #ifdef VDBE_PROFILE u64 start; /* CPU clock count at start of opcode */ int origPc; /* Program counter at start of opcode */ #endif + int nHit = 0; /* Cache hits for this call */ + int nMiss = 0; /* Cache misses for this call */ /*** INSERT STACK UNION HERE ***/ assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ sqlite3VdbeEnter(p); + vdbeCacheStats(p, &nHit, &nMiss); if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or ** sqlite3_column_text16() failed. */ goto no_mem; } @@ -6110,10 +6134,20 @@ /* This is the only way out of this procedure. We have to ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: db->lastRowid = lastRowid; + + /* Update the statement and database cache hit/miss statistics. */ + nHit = -nHit; + nMiss = -nMiss; + vdbeCacheStats(p, &nHit, &nMiss); + p->aCounter[SQLITE_STMTSTATUS_CACHE_HIT-1] += nHit; + p->aCounter[SQLITE_STMTSTATUS_CACHE_MISS-1] += nMiss; + db->aHitMiss[0] += nHit; + db->aHitMiss[1] += nMiss; + sqlite3VdbeLeave(p); return rc; /* Jump to here if a string or blob larger than SQLITE_MAX_LENGTH ** is encountered. Index: src/vdbeInt.h ================================================================== --- src/vdbeInt.h +++ src/vdbeInt.h @@ -308,11 +308,11 @@ u8 isPrepareV2; /* True if prepared with prepare_v2() */ int nChange; /* Number of db changes made since last reset */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ int iStatement; /* Statement number (or 0 if has not opened stmt) */ - int aCounter[3]; /* Counters used by sqlite3_stmt_status() */ + int aCounter[5]; /* Counters used by sqlite3_stmt_status() */ #ifndef SQLITE_OMIT_TRACE i64 startTime; /* Time when query started - used for profiling */ #endif i64 nFkConstraint; /* Number of imm. FK constraints this VM */ i64 nStmtDefCons; /* Number of def. constraints when stmt started */ Index: src/vdbeblob.c ================================================================== --- src/vdbeblob.c +++ src/vdbeblob.c @@ -13,10 +13,11 @@ ** This file contains code used to implement incremental BLOB I/O. */ #include "sqliteInt.h" #include "vdbeInt.h" +#include "btreeInt.h" #ifndef SQLITE_OMIT_INCRBLOB /* ** Valid sqlite3_blob* handles point to Incrblob structures. @@ -382,13 +383,21 @@ rc = SQLITE_ABORT; }else{ /* Call either BtreeData() or BtreePutData(). If SQLITE_ABORT is ** returned, clean-up the statement handle. */ + Pager *pPager = p->pCsr->pBt->pPager; + int nHit = 0; + int nMiss = 0; + assert( db == v->db ); sqlite3BtreeEnterCursor(p->pCsr); + sqlite3PagerCacheStats(pPager, &nHit, &nMiss); rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); + db->aHitMiss[0] -= nHit; + db->aHitMiss[1] -= nMiss; + sqlite3PagerCacheStats(pPager, &db->aHitMiss[0], &db->aHitMiss[1]); sqlite3BtreeLeaveCursor(p->pCsr); if( rc==SQLITE_ABORT ){ sqlite3VdbeFinalize(v); p->pStmt = 0; }else{ ADDED test/stmtstatus.test Index: test/stmtstatus.test ================================================================== --- /dev/null +++ test/stmtstatus.test @@ -0,0 +1,79 @@ +# 2011 September 20 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# +# Tests for the sqlite3_stmt_status() function +# + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +set ::testprefix stmtstatus + +do_execsql_test 1.0 { + PRAGMA page_size = 1024; + PRAGMA auto_vacuum = 0; + + CREATE TABLE t1(a PRIMARY KEY, b); + INSERT INTO t1 VALUES(1, randomblob(600)); + INSERT INTO t1 VALUES(2, randomblob(600)); + INSERT INTO t1 VALUES(3, randomblob(600)); +} + +proc stmt_hit_miss {stmt {reset 0}} { + list [sqlite3_stmt_status $stmt SQLITE_STMTSTATUS_CACHE_HIT $reset] \ + [sqlite3_stmt_status $stmt SQLITE_STMTSTATUS_CACHE_MISS $reset] +} + +do_test 1.1 { + db close + sqlite3 db test.db + expr {[file size test.db] / 1024} +} 6 + +do_test 1.2 { + set ::stmt [sqlite3_prepare_v2 db "SELECT b FROM t1 WHERE a=2" -1 dummy] + stmt_hit_miss $::stmt +} {0 0} + +breakpoint +do_test 1.3 { + sqlite3_step $::stmt + sqlite3_reset $::stmt +} SQLITE_OK +do_test 1.4 { stmt_hit_miss $::stmt } {1 3} +do_test 1.5 { + sqlite3_step $::stmt + sqlite3_reset $::stmt +} SQLITE_OK +do_test 1.6 { stmt_hit_miss $::stmt } {5 3} +do_test 1.7 { stmt_hit_miss $::stmt 0 } {5 3} +do_test 1.8 { stmt_hit_miss $::stmt 1 } {5 3} +do_test 1.9 { stmt_hit_miss $::stmt 0 } {0 0} +do_test 1.10 { sqlite3_finalize $::stmt } SQLITE_OK + +do_test 1.11 { sqlite3_db_status db CACHE_HIT 0 } {0 6 0} +do_test 1.12 { sqlite3_db_status db CACHE_MISS 0 } {0 3 0} +do_test 1.13 { sqlite3_db_status db CACHE_HIT 1 } {0 6 0} +do_test 1.14 { sqlite3_db_status db CACHE_MISS 1 } {0 3 0} +do_test 1.15 { sqlite3_db_status db CACHE_HIT 0 } {0 0 0} +do_test 1.16 { sqlite3_db_status db CACHE_MISS 0 } {0 0 0} + +do_test 1.17 { + set fd [db incrblob main t1 b 1] + set len [string length [read $fd]] + close $fd + set len +} 600 +do_test 1.18 { sqlite3_db_status db CACHE_HIT 0 } {0 2 0} +do_test 1.19 { sqlite3_db_status db CACHE_MISS 0 } {0 1 0} + + +finish_test