Index: src/env.c ================================================================== --- src/env.c +++ src/env.c @@ -40,12 +40,12 @@ 1, /* iVersion */ SQLITE4_DEFAULT_MEMSTATUS, /* bMemstat */ 1, /* bCoreMutex */ SQLITE4_THREADSAFE==1, /* bFullMutex */ 0x7ffffffe, /* mxStrlen */ - 128, /* szLookaside */ - 500, /* nLookaside */ + 0, /* szLookaside */ + 0, /* nLookaside */ &sqlite4MMSystem, /* pMM */ #if 0 {0,0,0,0,0,0,0,0,0}, /* m */ #endif {0,0,0,0,0,0,0,0,0,0}, /* mutex */ @@ -302,35 +302,28 @@ } #endif /* - ** sqlite4_env_config(p, SQLITE4_ENVCONFIG_MALLOC, sqlite4_mem_methods*) + ** sqlite4_env_config(p, SQLITE4_ENVCONFIG_SETMM, sqlite4_mm*) ** - ** Set the memory allocation routines to be used by this environment. + ** Set the memory allocator to be used by this environment. */ - case SQLITE4_ENVCONFIG_MALLOC: { -#if 0 - /* Specify an alternative malloc implementation */ + case SQLITE4_ENVCONFIG_SETMM: { if( pEnv->isInit ) return SQLITE4_MISUSE; - pEnv->m = *va_arg(ap, sqlite4_mem_methods*); -#endif + pEnv->pMM = va_arg(ap, sqlite4_mm*); break; } /* - ** sqlite4_env_config(p, SQLITE4_ENVCONFIG_GETMALLOC, sqlite4_mem_methods*) + ** sqlite4_env_config(p, SQLITE4_ENVCONFIG_GETMM, sqlite4_mm**) ** ** Copy the memory allocation routines in use by this environment ** into the structure given in the argument. */ - case SQLITE4_ENVCONFIG_GETMALLOC: { -#if 0 - /* Retrieve the current malloc() implementation */ - if( pEnv->m.xMalloc==0 ) sqlite4MemSetDefault(pEnv); - *va_arg(ap, sqlite4_mem_methods*) = pEnv->m; -#endif + case SQLITE4_ENVCONFIG_GETMM: { + *va_arg(ap, sqlite4_mm**) = pEnv->pMM; break; } /* sqlite4_env_config(p, SQLITE4_ENVCONFIG_MEMSTAT, int onoff); ** Index: src/mem.c ================================================================== --- src/mem.c +++ src/mem.c @@ -283,10 +283,193 @@ pOvfl->pB = pB; } return &pOvfl->base; } +/************************************************************************* +** The SQLITE4_MM_STATS memory allocator. +*/ + +/* +** Number of available statistics. Statistics are assigned ids starting at +** one, not zero. +*/ +#define MM_STATS_NSTAT 8 + +struct mmStats { + sqlite4_mm base; /* Base class. Must be first. */ + sqlite4_mm *p; /* Underlying allocator object */ + sqlite4_mutex *mutex; /* Mutex protecting aStat[] (or NULL) */ + + i64 nOut; /* Number of bytes outstanding */ + i64 nOutHw; /* Highwater mark of nOut */ + i64 nUnit; /* Number of allocations outstanding */ + i64 nUnitHw; /* Highwater mark of nUnit */ + i64 nMaxRequest; /* Largest request seen so far */ + i64 nFault; /* Number of malloc or realloc failures */ +}; + +static void updateStatsMalloc( + struct mmStats *pStats, + void *pNew, + sqlite4_size_t iSz +){ + /* Statistic SQLITE4_MMSTAT_SIZE records the largest allocation request + ** that has been made so far. So if iSz is larger than the current value, + ** set MMSTAT_SIZE to iSz now. This statistic is updated regardless of + ** whether or not the allocation succeeded. */ + if( iSz>pStats->nMaxRequest ){ + pStats->nMaxRequest = iSz; + } + + /* If the allocation succeeded, increase the number of allocations and + ** bytes outstanding accordingly. Also update the highwater marks if + ** required. If the allocation failed, increment the fault count. */ + if( pNew ){ + pStats->nOut += sqlite4_mm_msize(pStats->p, pNew); + pStats->nUnit += 1; + if( pStats->nOut>pStats->nOutHw ) pStats->nOutHw = pStats->nOut; + if( pStats->nUnit>pStats->nUnitHw ) pStats->nUnitHw = pStats->nUnit; + }else{ + pStats->nFault++; + } +} + +static void *mmStatsMalloc(sqlite4_mm *pMM, sqlite4_size_t iSz){ + struct mmStats *pStats = (struct mmStats*)pMM; + void *pRet; + + pRet = sqlite4_mm_malloc(pStats->p, iSz); + sqlite4_mutex_enter(pStats->mutex); + updateStatsMalloc(pStats, pRet, iSz); + sqlite4_mutex_leave(pStats->mutex); + return pRet; +} + +static void mmStatsFree(sqlite4_mm *pMM, void *pOld){ + struct mmStats *pStats = (struct mmStats*)pMM; + sqlite4_mutex_enter(pStats->mutex); + if( pOld ){ + sqlite4_size_t nByte = sqlite4_mm_msize(pMM, pOld); + pStats->nOut -= nByte; + pStats->nUnit -= 1; + } + sqlite4_mutex_leave(pStats->mutex); + sqlite4_mm_free(pStats->p, pOld); +} + +static void *mmStatsRealloc(sqlite4_mm *pMM, void *pOld, sqlite4_size_t iSz){ + struct mmStats *pStats = (struct mmStats*)pMM; + sqlite4_size_t nOrig = (pOld ? sqlite4_mm_msize(pStats->p, pOld) : 0); + void *pRet; + + pRet = sqlite4_mm_realloc(pStats->p, pOld, iSz); + + sqlite4_mutex_enter(pStats->mutex); + if( pRet ){ + pStats->nOut -= nOrig; + if( pOld ) pStats->nUnit--; + } + updateStatsMalloc(pStats, pRet, iSz); + sqlite4_mutex_leave(pStats->mutex); + + return pRet; +} + +static sqlite4_size_t mmStatsMsize(sqlite4_mm *pMM, void *pOld){ + struct mmStats *pStats = (struct mmStats*)pMM; + return sqlite4_mm_msize(pStats->p, pOld); +} + +static int mmStatsMember(sqlite4_mm *pMM, const void *pOld){ + struct mmStats *pStats = (struct mmStats*)pMM; + return sqlite4_mm_member(pStats->p, pOld); +} + + +static sqlite4_int64 mmStatsStat( + sqlite4_mm *pMM, + unsigned int eType, + unsigned int flags +){ + struct mmStats *pStats = (struct mmStats*)pMM; + i64 iRet = 0; + sqlite4_mutex_enter(pStats->mutex); + switch( eType ){ + case SQLITE4_MMSTAT_OUT: { + iRet = pStats->nOut; + break; + } + case SQLITE4_MMSTAT_OUT_HW: { + iRet = pStats->nOutHw; + if( flags & SQLITE4_MMSTAT_RESET ) pStats->nOutHw = pStats->nOut; + break; + } + case SQLITE4_MMSTAT_UNITS: { + iRet = pStats->nUnit; + break; + } + case SQLITE4_MMSTAT_UNITS_HW: { + iRet = pStats->nUnitHw; + if( flags & SQLITE4_MMSTAT_RESET ) pStats->nUnitHw = pStats->nUnit; + break; + } + case SQLITE4_MMSTAT_SIZE: { + iRet = pStats->nMaxRequest; + if( flags & SQLITE4_MMSTAT_RESET ) pStats->nMaxRequest = 0; + break; + } + case SQLITE4_MMSTAT_MEMFAULT: + case SQLITE4_MMSTAT_FAULT: { + iRet = pStats->nFault; + if( flags & SQLITE4_MMSTAT_RESET ) pStats->nFault = 0; + break; + } + } + sqlite4_mutex_leave(pStats->mutex); + return iRet; +} + +/* +** Destroy the allocator object passed as the first argument. +*/ +static void mmStatsFinal(sqlite4_mm *pMM){ + struct mmStats *pStats = (struct mmStats*)pMM; + sqlite4_mm *p = pStats->p; + sqlite4_mm_free(p, pStats); + sqlite4_mm_destroy(p); +} + +static const sqlite4_mm_methods mmStatsMethods = { + /* iVersion */ 1, + /* xMalloc */ mmStatsMalloc, + /* xRealloc */ mmStatsRealloc, + /* xFree */ mmStatsFree, + /* xMsize */ mmStatsMsize, + /* xMember */ mmStatsMember, + /* xBenign */ 0, + /* xStat */ mmStatsStat, + /* xCtrl */ 0, + /* xFinal */ mmStatsFinal +}; + +/* +** Allocate a new stats allocator. +*/ +static sqlite4_mm *mmStatsNew(sqlite4_mm *p){ + struct mmStats *pNew; + + pNew = (struct mmStats *)sqlite4_mm_malloc(p, sizeof(*pNew)); + if( pNew ){ + memset(pNew, 0, sizeof(*pNew)); + pNew->p = p; + pNew->base.pMethods = &mmStatsMethods; + } + + return (sqlite4_mm *)pNew; +} + /************************************************************************* ** The SQLITE4_MM_ONESIZE memory allocator. ** ** All memory allocations are rounded up to a single size, "sz". A request @@ -352,11 +535,15 @@ } static int mmOneszMember(sqlite4_mm *pMM, const void *pOld){ struct mmOnesz *pOnesz = (struct mmOnesz*)pMM; return pOld && pOld>=pOnesz->pSpace && pOld<=pOnesz->pLast; } -static sqlite4_int64 mmOneszStat(sqlite4_mm *pMM, int eType, unsigned flgs){ +static sqlite4_int64 mmOneszStat( + sqlite4_mm *pMM, + unsigned int eType, + unsigned int flgs +){ struct mmOnesz *pOnesz = (struct mmOnesz*)pMM; sqlite4_int64 x = -1; switch( eType ){ case SQLITE4_MMSTAT_OUT: { x = pOnesz->nUsed*pOnesz->sz; @@ -512,14 +699,19 @@ void *pSpace = va_arg(ap, void*); int sz = va_arg(ap, int); int cnt = va_arg(ap, int); pMM = mmOneszNew(pSpace, sz, cnt); break; + } + case SQLITE4_MM_STATS: { + sqlite4_mm *p = va_arg(ap, sqlite4_mm*); + pMM = mmStatsNew(p); + break; } default: { pMM = 0; break; } } va_end(ap); return pMM; } Index: src/prepare.c ================================================================== --- src/prepare.c +++ src/prepare.c @@ -594,10 +594,13 @@ int *pnUsed /* OUT: Bytes read from zSql */ ){ int rc; assert( ppStmt!=0 ); *ppStmt = 0; + if( pnUsed ){ + *pnUsed = 0; + } if( !sqlite4SafetyCheckOk(db) ){ return SQLITE4_MISUSE_BKPT; } sqlite4_mutex_enter(db->mutex); rc = sqlite4Prepare(db, zSql, nBytes, pOld, ppStmt, pnUsed); Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -168,11 +168,11 @@ ** has previously been freed, then the result of this routine is undefined. */ int sqlite4_mm_member(sqlite4_mm *pMM, const void *pOld); /* -** Allowed values for the second parameter ("eType") to sqlite4_mm_type(). +** Allowed values for the second parameter ("eType") to sqlite4_mm_stat(). */ #define SQLITE4_MMSTAT_OUT 1 #define SQLITE4_MMSTAT_OUT_HW 2 #define SQLITE4_MMSTAT_UNITS 3 #define SQLITE4_MMSTAT_UNITS_HW 4 @@ -195,11 +195,11 @@ sqlite4_int64 sqlite4_mm_stat(sqlite4_mm *pMM, int eType, unsigned flags); /* ** Send a control message into a memory allocator. */ -int sqlit4_mm_control(sqlite4_mm *pMM, int eType, ...); +int sqlite4_mm_control(sqlite4_mm *pMM, int eType, ...); /* ** Enable or disable benign failure mode. Benign failure mode can be ** nested. In benign failure mode, OOM errors do not necessarily propagate ** back out to the application but can be dealt with internally. Memory @@ -247,12 +247,12 @@ #define SQLITE4_ENVCONFIG_SINGLETHREAD 2 /* */ #define SQLITE4_ENVCONFIG_MULTITHREAD 3 /* */ #define SQLITE4_ENVCONFIG_SERIALIZED 4 /* */ #define SQLITE4_ENVCONFIG_MUTEX 5 /* sqlite4_mutex_methods* */ #define SQLITE4_ENVCONFIG_GETMUTEX 6 /* sqlite4_mutex_methods* */ -#define SQLITE4_ENVCONFIG_MALLOC 7 /* sqlite4_mem_methods* */ -#define SQLITE4_ENVCONFIG_GETMALLOC 8 /* sqlite4_mem_methods* */ +#define SQLITE4_ENVCONFIG_SETMM 7 /* sqlite4_mm* */ +#define SQLITE4_ENVCONFIG_GETMM 8 /* sqlite4_mm** */ #define SQLITE4_ENVCONFIG_MEMSTATUS 9 /* boolean */ #define SQLITE4_ENVCONFIG_LOOKASIDE 10 /* size, count */ #define SQLITE4_ENVCONFIG_LOG 11 /* xLog, pArg */ #define SQLITE4_ENVCONFIG_KVSTORE_PUSH 12 /* name, factory */ #define SQLITE4_ENVCONFIG_KVSTORE_POP 13 /* name */ Index: src/tclsqlite.c ================================================================== --- src/tclsqlite.c +++ src/tclsqlite.c @@ -2877,11 +2877,10 @@ extern int SqliteSuperlock_Init(Tcl_Interp*); extern int SqlitetestSyscall_Init(Tcl_Interp*); extern int Sqliteteststorage_Init(Tcl_Interp*); extern int Sqliteteststorage2_Init(Tcl_Interp*); extern int SqlitetestLsm_Init(Tcl_Interp*); - extern int Sqlitetest_mem_Init(Tcl_Interp*); extern void sqlite4TestInit(Tcl_Interp*); Sqliteconfig_Init(interp); Sqlitetest1_Init(interp); @@ -2893,13 +2892,11 @@ Sqlitetest_mutex_Init(interp); SqlitetestThread_Init(interp); Sqliteteststorage_Init(interp); Sqliteteststorage2_Init(interp); SqlitetestLsm_Init(interp); - Sqlitetest_mem_Init(interp); sqlite4TestInit(interp); - Tcl_CreateObjCommand( interp, "load_testfixture_extensions", init_all_cmd, 0, 0 ); Tcl_CreateObjCommand( Index: test/testInt.h ================================================================== --- test/testInt.h +++ test/testInt.h @@ -23,7 +23,10 @@ void sqlite4TestInit(Tcl_Interp*); void *sqlite4TestTextToPtr(const char *z); int sqlite4TestDbHandle(Tcl_Interp *, Tcl_Obj *, sqlite4 **); int sqlite4TestSetResult(Tcl_Interp *interp, int rc); +sqlite4_mm *test_mm_debug(sqlite4_mm *p); +void test_mm_debug_report(sqlite4_mm *p, FILE *pFile); + #endif Index: test/test_malloc.c ================================================================== --- test/test_malloc.c +++ test/test_malloc.c @@ -17,10 +17,12 @@ #include "tcl.h" #include #include #include +#include "testInt.h" + /* ** This structure is used to encapsulate the global state variables used ** by malloc() fault simulation. */ static struct MemFault { @@ -180,10 +182,11 @@ /* ** Add or remove the fault-simulation layer using sqlite4_env_config(). If ** the argument is non-zero, the */ static int faultsimInstall(int install){ +#if 0 static struct sqlite4_mem_methods m = { faultsimMalloc, /* xMalloc */ faultsimFree, /* xFree */ faultsimRealloc, /* xRealloc */ faultsimSize, /* xSize */ @@ -225,10 +228,11 @@ if( rc==SQLITE4_OK ){ memfault.isInstalled = 1; } return rc; +#endif } #ifdef SQLITE4_TEST /* @@ -1118,10 +1122,144 @@ rc = sqlite4_env_config(0, SQLITE4_ENVCONFIG_MALLOC, sqlite4MemGetMemsys3()); #endif Tcl_SetResult(interp, (char *)sqlite4TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } + +static sqlite4_mm *pMMDebug = 0; + +/* +** tclcmd: test_mm_install ?debug? ?backtrace? ?stats? +*/ +static int test_mm_install( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + const char *azOpt[] = { + "debug", /* 0 */ + "backtrace", /* 1 */ + "stats", /* 2 */ + 0 + }; + sqlite4_mm *pMM = 0; + int i; + int bDebug = 0; + int bBacktrace = 0; + int bStats = 0; + + for(i=1; i #include #include -#include "sqlite4.h" +#include "sqliteInt.h" +#include "testInt.h" #define ArraySize(x) ((int)(sizeof(x) / sizeof((x)[0]))) #define MIN(x,y) ((x)<(y) ? (x) : (y)) @@ -34,22 +35,24 @@ #endif typedef struct TmBlockHdr TmBlockHdr; typedef struct TmAgg TmAgg; -typedef struct TmGlobal TmGlobal; +typedef struct Testmem Testmem; + +/* +** The object that implements the sqlite4_mm interface for this allocator. +*/ +struct Testmem { + sqlite4_mm base; /* Base class. Must be first. */ + sqlite4_mm *p; /* Underlying allocator object */ + sqlite4_mutex *mutex; /* Mutex protecting this object (or NULL) */ -struct TmGlobal { - /* Linked list of all currently outstanding allocations. And a table of - ** all allocations, past and present, indexed by backtrace() info. */ - TmBlockHdr *pFirst; + TmBlockHdr *pFirst; /* List of all outstanding allocations */ #ifdef TM_BACKTRACE - TmAgg *aHash[10000]; + TmAgg *aHash[10000]; /* Table of all allocations by backtrace() */ #endif - - /* Underlying malloc/realloc/free functions */ - sqlite4_mem_methods mem; }; struct TmBlockHdr { TmBlockHdr *pNext; TmBlockHdr *pPrev; @@ -76,37 +79,45 @@ static const u32 rearguard = REARGUARD; #define ROUND8(x) (((x)+7)&~7) #define BLOCK_HDR_SIZE (ROUND8( sizeof(TmBlockHdr) )) -static void tmEnterMutex(TmGlobal *pTm){ - /*pTm->xEnterMutex(pTm);*/ -} -static void tmLeaveMutex(TmGlobal *pTm){ - /* pTm->xLeaveMutex(pTm); */ +/* +** Given a user data pointer, return a pointer to the associated +** TmBlockHdr structure. +*/ +static TmBlockHdr *userToBlock(const void *p){ + return (TmBlockHdr *)(((const u8 *)p) - BLOCK_HDR_SIZE); } -static void *tmMalloc(TmGlobal *pTm, int nByte){ +/* +** sqlite4_mm_methods.xMalloc method. +*/ +static void *mmDebugMalloc(sqlite4_mm *pMM, sqlite4_size_t nByte){ + Testmem *pTest = (Testmem *)pMM; TmBlockHdr *pNew; /* New allocation header block */ u8 *pUser; /* Return value */ int nReq; /* Total number of bytes requested */ + /* Allocate space for the users allocation, the TmBlockHdr object that + ** located immediately before the users allocation in memory, and the + ** 4-byte 'rearguard' located immediately following it. */ assert( sizeof(rearguard)==4 ); nReq = BLOCK_HDR_SIZE + nByte + 4; - pNew = (TmBlockHdr *)pTm->mem.xMalloc(pTm->mem.pMemEnv, nReq); + pNew = (TmBlockHdr *)sqlite4_mm_malloc(pTest->p, nReq); memset(pNew, 0, sizeof(TmBlockHdr)); - tmEnterMutex(pTm); + sqlite4_mutex_enter(pTest->mutex); pNew->iForeGuard = FOREGUARD; pNew->nByte = nByte; - pNew->pNext = pTm->pFirst; + pNew->pNext = pTest->pFirst; - if( pTm->pFirst ){ - pTm->pFirst->pPrev = pNew; + if( pTest->pFirst ){ + pTest->pFirst->pPrev = pNew; } - pTm->pFirst = pNew; + pTest->pFirst = pNew; pUser = &((u8 *)pNew)[BLOCK_HDR_SIZE]; memset(pUser, 0x56, nByte); memcpy(&pUser[nByte], &rearguard, 4); @@ -120,50 +131,55 @@ backtrace(aFrame, TM_BACKTRACE); for(i=0; iaHash); + iHash = iHash % ArraySize(pTest->aHash); - for(pAgg=pTm->aHash[iHash]; pAgg; pAgg=pAgg->pNext){ + for(pAgg=pTest->aHash[iHash]; pAgg; pAgg=pAgg->pNext){ if( memcmp(pAgg->aFrame, aFrame, sizeof(aFrame))==0 ) break; } if( !pAgg ){ - pAgg = (TmAgg *)pTm->mem.xMalloc(pTm->mem.pMemEnv, sizeof(TmAgg)); + pAgg = (TmAgg *)sqlite4_mm_malloc(pTest->p, sizeof(TmAgg)); memset(pAgg, 0, sizeof(TmAgg)); memcpy(pAgg->aFrame, aFrame, sizeof(aFrame)); - pAgg->pNext = pTm->aHash[iHash]; - pTm->aHash[iHash] = pAgg; + pAgg->pNext = pTest->aHash[iHash]; + pTest->aHash[iHash] = pAgg; } pAgg->nAlloc++; pAgg->nByte += nByte; pAgg->nOutAlloc++; pAgg->nOutByte += nByte; pNew->pAgg = pAgg; } #endif - tmLeaveMutex(pTm); + sqlite4_mutex_leave(pTest->mutex); return pUser; } -static void tmFree(TmGlobal *pTm, void *p){ +/* +** sqlite4_mm_methods.xFree method. +*/ +static void mmDebugFree(sqlite4_mm *pMM, void *p){ + Testmem *pTest = (Testmem *)pMM; if( p ){ - TmBlockHdr *pHdr; + TmBlockHdr *pHdr = userToBlock(p); u8 *pUser = (u8 *)p; - tmEnterMutex(pTm); - pHdr = (TmBlockHdr *)&pUser[BLOCK_HDR_SIZE * -1]; + sqlite4_mutex_enter(pTest->mutex); + assert( pHdr->iForeGuard==FOREGUARD ); assert( 0==memcmp(&pUser[pHdr->nByte], &rearguard, 4) ); + /* Unlink the TmBlockHdr object from the (Testmem.pFirst) list. */ if( pHdr->pPrev ){ assert( pHdr->pPrev->pNext==pHdr ); pHdr->pPrev->pNext = pHdr->pNext; }else{ - assert( pHdr==pTm->pFirst ); - pTm->pFirst = pHdr->pNext; + assert( pHdr==pTest->pFirst ); + pTest->pFirst = pHdr->pNext; } if( pHdr->pNext ){ assert( pHdr->pNext->pPrev==pHdr ); pHdr->pNext->pPrev = pHdr->pPrev; } @@ -171,257 +187,139 @@ #ifdef TM_BACKTRACE pHdr->pAgg->nOutAlloc--; pHdr->pAgg->nOutByte -= pHdr->nByte; #endif - tmLeaveMutex(pTm); - memset(pUser, 0x58, pHdr->nByte); - memset(pHdr, 0x57, sizeof(TmBlockHdr)); - pTm->mem.xFree(pTm->mem.pMemEnv, pHdr); - } -} - -static void *tmRealloc(TmGlobal *pTm, void *p, int nByte){ - void *pNew; - - pNew = tmMalloc(pTm, nByte); - if( pNew && p ){ - TmBlockHdr *pHdr; - u8 *pUser = (u8 *)p; - pHdr = (TmBlockHdr *)&pUser[BLOCK_HDR_SIZE * -1]; - memcpy(pNew, p, MIN(nByte, pHdr->nByte)); - tmFree(pTm, p); - } - return pNew; -} - -static void tmMallocCheck( - TmGlobal *pTm, - int *pnLeakAlloc, - int *pnLeakByte, - FILE *pFile -){ - TmBlockHdr *pHdr; - int nLeak = 0; - int nByte = 0; - - if( pTm==0 ) return; - - for(pHdr=pTm->pFirst; pHdr; pHdr=pHdr->pNext){ - nLeak++; - nByte += pHdr->nByte; - } - if( pnLeakAlloc ) *pnLeakAlloc = nLeak; - if( pnLeakByte ) *pnLeakByte = nByte; - -#ifdef TM_BACKTRACE - if( pFile ){ - int i; - fprintf(pFile, "LEAKS\n"); - for(i=0; iaHash); i++){ - TmAgg *pAgg; - for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){ - if( pAgg->nOutAlloc ){ - int j; - fprintf(pFile, "%d %d ", pAgg->nOutByte, pAgg->nOutAlloc); - for(j=0; jaFrame[j]); - } - fprintf(pFile, "\n"); - } - } - } - fprintf(pFile, "\nALLOCATIONS\n"); - for(i=0; iaHash); i++){ - TmAgg *pAgg; - for(pAgg=pTm->aHash[i]; pAgg; pAgg=pAgg->pNext){ - int j; - fprintf(pFile, "%d %d ", pAgg->nByte, pAgg->nAlloc); - for(j=0; jaFrame[j]); - fprintf(pFile, "\n"); - } - } - } -#else - (void)pFile; -#endif -} - - -static void *tmLsmEnvXMalloc(void *p, sqlite4_size_t n){ - return tmMalloc( (TmGlobal*) p, (int)n ); -} - -static void tmLsmEnvXFree(void *p, void *ptr){ - tmFree( (TmGlobal *)p, ptr ); -} - -static void *tmLsmEnvXRealloc(void *ptr, void * mem, int n){ - return tmRealloc((TmGlobal*)ptr, mem, n); -} - - -static sqlite4_size_t tmLsmXSize(void *p, void *ptr){ - if(NULL==ptr){ - return 0; - }else{ - unsigned char * pUc = (unsigned char *)ptr; - TmBlockHdr * pBlock = (TmBlockHdr*)(pUc-BLOCK_HDR_SIZE); - assert( pBlock->nByte > 0 ); - return (sqlite4_size_t) pBlock->nByte; - } -} - -static int tmInitStub(void* ignored){ - assert("Set breakpoint here."); - return 0; -} -static void tmVoidStub(void* ignored){} - - -int testMallocInstall(sqlite4_env *pEnv){ - TmGlobal *pGlobal; /* Object containing allocation hash */ - sqlite4_mem_methods allocator; /* This malloc system */ - sqlite4_mem_methods orig; /* Underlying malloc system */ - - /* Allocate and populate a TmGlobal structure. sqlite4_malloc cannot be - ** used to allocate the TmGlobal struct as this would cause the environment - ** to move to "initialized" state and the SQLITE4_ENVCONFIG_MALLOC - ** to fail. */ - sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &orig); - pGlobal = (TmGlobal *)orig.xMalloc(orig.pMemEnv, sizeof(TmGlobal)); - memset(pGlobal, 0, sizeof(TmGlobal)); - memcpy(&pGlobal->mem, &orig, sizeof(orig)); - - /* Set up pEnv to the use the new TmGlobal */ - allocator.xRealloc = tmLsmEnvXRealloc; - allocator.xMalloc = tmLsmEnvXMalloc; - allocator.xFree = tmLsmEnvXFree; - allocator.xSize = tmLsmXSize; - allocator.xInit = tmInitStub; - allocator.xShutdown = tmVoidStub; - allocator.xBeginBenign = tmVoidStub; - allocator.xEndBenign = tmVoidStub; - allocator.pMemEnv = pGlobal; - return sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_MALLOC, &allocator); -} - -int testMallocUninstall(sqlite4_env *pEnv){ - TmGlobal *pGlobal; /* Object containing allocation hash */ - sqlite4_mem_methods allocator; /* This malloc system */ - int rc; - - sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &allocator); - assert( allocator.xMalloc==tmLsmEnvXMalloc ); - pGlobal = (TmGlobal *)allocator.pMemEnv; - - rc = sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_MALLOC, &pGlobal->mem); - if( rc==SQLITE4_OK ){ - sqlite4_free(pEnv, pGlobal); - } - return rc; -} - -void testMallocCheck( - sqlite4_env *pEnv, - int *pnLeakAlloc, - int *pnLeakByte, - FILE *pFile -){ - TmGlobal *pGlobal; - sqlite4_mem_methods allocator; /* This malloc system */ - - sqlite4_env_config(pEnv, SQLITE4_ENVCONFIG_GETMALLOC, &allocator); - assert( allocator.xMalloc==tmLsmEnvXMalloc ); - pGlobal = (TmGlobal *)allocator.pMemEnv; - - tmMallocCheck(pGlobal, pnLeakAlloc, pnLeakByte, pFile); -} - -#include - -/* -** testmem install -** testmem uninstall -** testmem report ?FILENAME? -*/ -static int testmem_cmd( - void * clientData, - Tcl_Interp *interp, - int objc, - Tcl_Obj *CONST objv[] -){ - sqlite4_env *pEnv; /* SQLite 4 environment to work with */ - int iOpt; - const char *azSub[] = {"install", "uninstall", "report", 0}; - - if( objc<2 ){ - Tcl_WrongNumArgs(interp, 1, objv, "sub-command"); - return TCL_ERROR; - } - if( Tcl_GetIndexFromObj(interp, objv[1], azSub, "sub-command", 0, &iOpt) ){ - return TCL_ERROR; - } - - pEnv = sqlite4_env_default(); - switch( iOpt ){ - case 0: { - int rc; - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 2, objv, ""); - return TCL_ERROR; - } - rc = testMallocInstall(pEnv); - if( rc!=SQLITE4_OK ){ - Tcl_AppendResult(interp, "Failed to install testmem wrapper", 0); - return TCL_ERROR; - } - break; - } - - case 1: - if( objc!=2 ){ - Tcl_WrongNumArgs(interp, 2, objv, ""); - return TCL_ERROR; - } - testMallocUninstall(pEnv); - break; - - case 2: { - int nLeakAlloc = 0; - int nLeakByte = 0; - FILE *pReport = 0; - Tcl_Obj *pRes; - - if( objc!=2 && objc!=3 ){ - Tcl_WrongNumArgs(interp, 2, objv, "?filename?"); - return TCL_ERROR; - } - if( objc==3 ){ - const char *zFile = Tcl_GetString(objv[2]); - pReport = fopen(zFile, "w"); - if( !pReport ){ - Tcl_AppendResult(interp, "Failed to open file: ", zFile, 0); - return TCL_ERROR; - } - } - - testMallocCheck(pEnv, &nLeakAlloc, &nLeakByte, pReport); - if( pReport ) fclose(pReport); - - pRes = Tcl_NewObj(); - Tcl_ListObjAppendElement(interp, pRes, Tcl_NewIntObj(nLeakAlloc)); - Tcl_ListObjAppendElement(interp, pRes, Tcl_NewIntObj(nLeakByte)); - Tcl_SetObjResult(interp, pRes); - break; - } - } - - return TCL_OK; -} - -int Sqlitetest_mem_Init(Tcl_Interp *interp){ - Tcl_CreateObjCommand(interp, "testmem", testmem_cmd, 0, 0); - return TCL_OK; + sqlite4_mutex_leave(pTest->mutex); + + memset(pUser, 0x58, pHdr->nByte); + memset(pHdr, 0x57, sizeof(TmBlockHdr)); + sqlite4_mm_free(pTest->p, pHdr); + } +} + +/* +** sqlite4_mm_methods.xRealloc method. +*/ +static void *mmDebugRealloc(sqlite4_mm *pMM, void *p, int nByte){ + void *pNew; + + pNew = sqlite4_mm_malloc(pMM, nByte); + if( pNew && p ){ + int nOrig = sqlite4_mm_msize(pMM, p); + memcpy(pNew, p, MIN(nByte, nOrig)); + sqlite4_mm_free(pMM, p); + } + + return pNew; +} + +/* +** sqlite4_mm_methods.xMsize method. +*/ +static sqlite4_size_t mmDebugMsize(sqlite4_mm *pMM, void *p){ + if( p==0 ) return 0; + TmBlockHdr *pHdr = userToBlock(p); + return pHdr->nByte; +} + +/* +** sqlite4_mm_methods.xMember method. +*/ +static int mmDebugMember(sqlite4_mm *pMM, const void *p){ + Testmem *pTest = (Testmem *)pMM; + return sqlite4_mm_member(pTest->p, (const void *)userToBlock(p)); +} + +/* +** sqlite4_mm_methods.xStat method. +*/ +static sqlite4_int64 mmDebugStat( + sqlite4_mm *pMM, + unsigned int eType, + unsigned int flags +){ + Testmem *pTest = (Testmem *)pMM; + return sqlite4_mm_stat(pTest->p, eType, flags); +} + +/* +** Write a report of all mallocs, outstanding and otherwise, to the +** file passed as the second argument. +*/ +static void mmDebugReport(Testmem *pTest, FILE *pFile){ +#ifdef TM_BACKTRACE + int i; + fprintf(pFile, "LEAKS\n"); + for(i=0; iaHash); i++){ + TmAgg *pAgg; + for(pAgg=pTest->aHash[i]; pAgg; pAgg=pAgg->pNext){ + if( pAgg->nOutAlloc ){ + int j; + fprintf(pFile, "%d %d ", pAgg->nOutByte, pAgg->nOutAlloc); + for(j=0; jaFrame[j]); + } + fprintf(pFile, "\n"); + } + } + } + fprintf(pFile, "\nALLOCATIONS\n"); + for(i=0; iaHash); i++){ + TmAgg *pAgg; + for(pAgg=pTest->aHash[i]; pAgg; pAgg=pAgg->pNext){ + int j; + fprintf(pFile, "%d %d ", pAgg->nByte, pAgg->nAlloc); + for(j=0; jaFrame[j]); + fprintf(pFile, "\n"); + } + } +#else + (void)pFile; + (void)pTest; +#endif +} + +/* +** Destroy the allocator object passed as the first argument. +*/ +static void mmDebugFinal(sqlite4_mm *pMM){ + Testmem *pTest = (Testmem *)pMM; + sqlite4_mm *p = pTest->p; + sqlite4_mm_free(p, (void *)pTest); + sqlite4_mm_destroy(p); +} + +/* +** Create a new debug allocator wrapper around the allocator passed as the +** first argument. +*/ +sqlite4_mm *test_mm_debug(sqlite4_mm *p){ + static const sqlite4_mm_methods mmDebugMethods = { + /* iVersion */ 1, + /* xMalloc */ mmDebugMalloc, + /* xRealloc */ mmDebugRealloc, + /* xFree */ mmDebugFree, + /* xMsize */ mmDebugMsize, + /* xMember */ mmDebugMember, + /* xBenign */ 0, + /* xStat */ mmDebugStat, + /* xCtrl */ 0, + /* xFinal */ mmDebugFinal + }; + Testmem *pTest; + pTest = (Testmem *)sqlite4_mm_malloc(p, sizeof(Testmem)); + if( pTest ){ + memset(pTest, 0, sizeof(Testmem)); + pTest->base.pMethods = &mmDebugMethods; + pTest->p = p; + } + return (sqlite4_mm *)pTest; +} + +void test_mm_debug_report(sqlite4_mm *p, FILE *pFile){ + Testmem *pTest = (Testmem *)p; + assert( pTest->base.pMethods->xMalloc==mmDebugMalloc ); + mmDebugReport(pTest, pFile); } Index: test/tester.tcl ================================================================== --- test/tester.tcl +++ test/tester.tcl @@ -83,10 +83,12 @@ # very large database files. # set tcl_precision 15 #sqlite4_test_control_pending_byte 0x0010000 +test_mm_install stats debug + # If the pager codec is available, create a wrapper for the [sqlite4] # command that appends "-key {xyzzy}" to the command line. i.e. this: # # sqlite4 db test.db @@ -761,10 +763,21 @@ } if {$::cmdlinearg(binarylog)} { vfslog finalize binarylog } kvwrap uninstall + + set nOut [test_mm_stat out] + set nUnit [test_mm_stat units] + if {$nOut!=0 || $nUnit!=0} { + puts "Unfreed memory: $nOut bytes in $nUnit allocations" + } else { + puts "All memory allocations freed - no leaks" + } + show_memstats + test_mm_report malloc.txt + # if {[lindex [sqlite4_env_status SQLITE4_ENVSTATUS_MALLOC_COUNT 0] 1]>0 || # [sqlite4_memory_used]>0} { # puts "Unfreed memory: [sqlite4_memory_used] bytes in\ # [lindex [sqlite4_env_status SQLITE4_ENVSTATUS_MALLOC_COUNT 0] 1] allocations" # incr nErr @@ -776,16 +789,15 @@ # puts "All memory allocations freed - no leaks" # ifcapable memdebug||mem5 { # sqlite4_memdebug_dump ./memusage.txt # } # } - show_memstats #puts "Maximum memory usage: [sqlite4_memory_highwater 1] bytes" #puts "Current memory usage: [sqlite4_memory_highwater] bytes" - if {[info commands sqlite4_memdebug_malloc_count] ne ""} { - puts "Number of malloc() : [sqlite4_memdebug_malloc_count] calls" - } + #if {[info commands sqlite4_memdebug_malloc_count] ne ""} { + # puts "Number of malloc() : [sqlite4_memdebug_malloc_count] calls" + #} if {$::cmdlinearg(malloctrace)} { puts "Writing malloc() report to malloc.txt..." testmem report malloc.txt } foreach f [glob -nocomplain test.db-*-journal] { @@ -798,18 +810,22 @@ } # Display memory statistics for analysis and debugging purposes. # proc show_memstats {} { - set x [sqlite4_env_status SQLITE4_ENVSTATUS_MEMORY_USED 0] - set y [sqlite4_env_status SQLITE4_ENVSTATUS_MALLOC_SIZE 0] - set val [format {now %10d max %10d max-size %10d} \ - [lindex $x 1] [lindex $x 2] [lindex $y 2]] - puts "Memory used: $val" - set x [sqlite4_env_status SQLITE4_ENVSTATUS_MALLOC_COUNT 0] - set val [format {now %10d max %10d} [lindex $x 1] [lindex $x 2]] - puts "Allocation count: $val" + set nOut [test_mm_stat out] + set nOutHw [test_mm_stat out_hw] + set nMaxRequest [test_mm_stat size] + set nUnit [test_mm_stat units] + set nUnitHw [test_mm_stat units_hw] + + puts [format \ + "Memory used: now %-10d max %-10d max-size %-10d" \ + $nOut $nOutHw $nMaxRequest \ + ] + puts [format "Allocations: now %-10d max %-10d" $nUnit $nUnitHw] + ifcapable yytrackmaxstackdepth { set x [sqlite4_env_status SQLITE4_ENVSTATUS_PARSER_STACK 0] set val [format { max %10d} [lindex $x 2]] puts "Parser stack depth: $val" }