rem: bufPtr = ns_malloc(sizeof(Tcl_HashTable));
add: bufPtr = ns_malloc(sizeof(Buf));
context: static Ns_Callback FreeBuf;static void FlushBuf(Buf *bufPtr);static Ns_TclInterpInitProc AddCmds;static Ns_TclTraceProc CreateTrace;static Ns_TclTraceProc FlushBufTrace;static Tcl_CmdTraceProc StatsTrace;static Tcl_HashTable statsTable;static void IncrCount(Tcl_HashTable *tablePtr, char *cmd, unsigned long count);static int SortName(const void *a1, const void *a2);static int SortCount(const void *a1, const void *a2);static Ns_Tls tls;static Ns_Mutex lock;/* *---------------------------------------------------------------------- * * NsTclStatsInit -- * *	Initialize the Tcl stats, registering a callback called when *	an interp is created so that the stats trace can be created. *	If buffering is enabled, also register a cleanup procedure *	to flush buffered counts when the interp is deallocated (e.g., *	at the end of a connection). * * Results: *	None. * * Side effects: *	Callbacks, TLS may be created. * *---------------------------------------------------------------------- */voidNsTclStatsInit(void){    Tcl_InitHashTable(&statsTable, TCL_STRING_KEYS);    Ns_MutexSetName2(&lock, "ns", "tclstats");    if (nsconf.tcl.statlevel > 0) {	Ns_Log(Warning, "tclstats: tracing to level %d", nsconf.tcl.statlevel);	Ns_TclRegisterAtCreate(CreateTrace, NULL);	if (nsconf.tcl.statmaxbuf > 0) {	    Ns_TlsAlloc(&tls, FreeBuf);	    Ns_TclRegisterAtCleanup(FlushBufTrace, NULL);	}    }}/* *---------------------------------------------------------------------- * * NsTclStatsCmds -- * *	Implemented the ns_stats command. * * Results: *	Standard Tcl result. * * Side effects: *	None. * *---------------------------------------------------------------------- */intNsTclStatsCmd(ClientData ignored, Tcl_Interp *interp, int argc, char **argv){    Tcl_HashEntry *hPtr, **list;    Tcl_HashSearch search;    Tcl_DString ds;    char buf[100];    int i, nlist;    int (*compare)(const void *, const void *);    char *pattern, *path;    if (argc > 3) {	Tcl_AppendResult(interp, "wrong # args: should be \"",	    argv[0], " ?option? ?pattern?\"", NULL);	return TCL_ERROR;    }    compare = SortCount;    if (argc == 1) {	pattern = NULL;    } else {	pattern = argv[2];	if (!strcmp(argv[1], "-count")) {	    compare = SortCount;	} else if (!strcmp(argv[1], "-name")) {	    compare = SortName;	} else if (argc == 3) {	    Tcl_AppendResult(interp, "invalid option \"", argv[1],		"\": should be -count or -name", NULL);	    return TCL_ERROR;	} else {	    pattern = argv[1];	}    }    Tcl_DStringInit(&ds);    if (statsTable.numEntries > 0) {	list = ns_malloc(sizeof(Tcl_HashEntry *) * statsTable.numEntries);	nlist = 0;	hPtr = Tcl_FirstHashEntry(&statsTable, &search);	while (hPtr != NULL) {	    path = Tcl_GetHashKey(&statsTable, hPtr);	    if (pattern == NULL || Tcl_StringMatch(path, pattern)) {	    	list[nlist++] = hPtr;	    }	    hPtr = Tcl_NextHashEntry(&search);	}	if (nlist > 0) {	    qsort(list, nlist, sizeof(Tcl_HashEntry *), compare);	    for (i = 0; i < nlist; ++i) {	        hPtr = list[i];	    	path = Tcl_GetHashKey(&statsTable, hPtr);    	    	sprintf(buf, "%ld", (unsigned long) Tcl_GetHashValue(hPtr));	    	Tcl_DStringAppendElement(&ds, path);	    	Tcl_DStringAppendElement(&ds, buf);	    	Tcl_AppendElement(interp, ds.string);	    	Tcl_DStringTrunc(&ds, 0);	    }	}	ns_free(list);    }    Tcl_DStringFree(&ds);    return TCL_OK;}/* *---------------------------------------------------------------------- * * CreateTrace -- * *	Create the stats trace when a thread's interp is created. * * Results: *	None. * * Side effects: *	StatsTrace will be called before each command. * *---------------------------------------------------------------------- */static intCreateTrace(Tcl_Interp *interp, void *ignored){    Tcl_CreateTrace(interp, nsconf.tcl.statlevel, StatsTrace, NULL);    return TCL_OK;}/* *---------------------------------------------------------------------- * * StatsTrace -- * *	Call a user-supplied Tcl initialization procedure in the parent *  	interp. * *  	NOTE:  This routine only allows execution in the initial thread *  	during startup. * * Results: *	Tcl result from user's procedure. * * Side effects: *	Parent interp used as a template for all other per-thread interps *	is created. * *---------------------------------------------------------------------- */static voidStatsTrace(ClientData clientData, Tcl_Interp *interp, int level,	char *command, Tcl_CmdProc *cmdProc, ClientData cmdClientData,	int argc, char **argv){    Buf *bufPtr;    /*     * If buffering is not enabled, update the global table directly     * (this could be a source of lock contention).  Otherwise, update     * this thread's table, flushing if the buffer limit is exceeded.     */    if (nsconf.tcl.statmaxbuf <= 0) {	Ns_MutexLock(&lock);	IncrCount(&statsTable, argv[0], 1);	Ns_MutexUnlock(&lock);    } else {    	bufPtr = Ns_TlsGet(&tls);    	if (bufPtr == NULL) {	    bufPtr = ns_malloc(sizeof(Tcl_HashTable));	    bufPtr->nbuf = 0;	    Tcl_InitHashTable(&bufPtr->table, TCL_STRING_KEYS);	    Ns_TlsSet(&tls, bufPtr);    	}	IncrCount(&bufPtr->table, argv[0], 1);    	if (++bufPtr->nbuf > nsconf.tcl.statmaxbuf) {	    FlushBuf(bufPtr);    	}    }}/* *---------------------------------------------------------------------- * * IncrCount -- * *	Increment the usage count in a table. * * Results: *	None. * * Side effects: *	None. * *---------------------------------------------------------------------- */static voidIncrCount(Tcl_HashTable *tablePtr, char *cmd, unsigned long count){    Tcl_HashEntry *hPtr;    int new;    hPtr = Tcl_CreateHashEntry(tablePtr, cmd, &new);    count += (unsigned long) Tcl_GetHashValue(hPtr);    Tcl_SetHashValue(hPtr, (void *) count);}/* *---------------------------------------------------------------------- * * FreeBuf -- * *	TLS callback to cleanup a thread's buffer. * * Results: *	None. * * Side effects: *	Remaining counts (if any) are flushed. * *---------------------------------------------------------------------- */static voidFreeBuf(void *arg){    Buf *bufPtr = arg;    FlushBuf(bufPtr);    Tcl_DeleteHashTable(&bufPtr->table);    ns_free(bufPtr);}