Search Search

#1 worldwide
FREE Coding Lessons

since 1996
   THE BEST WAY to learn ASP & Asp.net!
Advertise Here!
click for details
Credits Host:
DiscountASP.net
Server Admin:
The "Team"
Contact Info.
Charles M. Carroll

my Blog
[prev. Lesson]  RsFast: Library Source Code
     [next Lesson]  RSFast: caching Method Explained

RSFast - Jscript Version
by Sterling Bates

Sterling has kindly re-written the library Rsfast in server Jscript. Here is a Jscript program calling the Library:

   filename=/learn/rsfast_current/test_js_rsfast.asp

<Test Script Below>


<%@ LANGUAGE="JSCRIPT"%>
<%
var rp = Response;

rp.Buffer = true;
Server.ScriptTimeout = 40;
%>
<!--#include file="lib_rsfast_js.asp"-->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
    <title>rsfast-templates</title>
</head>
<body bgcolor="#FFFFFF">
<%

    var conntest = new String( "DSN=student" );
    var rsparms = Server.CreateObject( "Scripting.Dictionary" );

    rsparms.Add( "conn", conntest );
    rsparms.Add( "sql", "select * from publishers where state='NY'" );
    rsparms.Add( "accdb","students.mdb" );
    rsparms.Add( "template_header", "<table border=1>" );
    rsparms.Add( "template_row_header", "<tr>" );
    rsparms.Add( "template_row_footer", "</tr>" );
    rsparms.Add( "template_col_header", "<td>" );
    rsparms.Add( "template_col_footer", "</td>" );
    rsparms.Add( "template_footer", "</table>" );
    rsparms.Add( "fieldnull", "&nbsp;" );
    rsparms.Add( "fieldblank", "&nbsp;" );
    rsparms.Add( "fld_city", "<td bgcolor='lightblue'><b>{fld}</b><br></td>" );
    rsparms.Add( "fld_state", "<td><b>{fld}</b><br></td>"  );
    rsparms.Add( "colnames", "display"  );

    rsfast = new CCacheFast( rsparms );
    rsfast.run();
    rsfast.perf.show();
    rsfast.perf.clear();

    rsfast = null;
    rsparms = null;
%>

Sterling has kindly re-written the library Rsfast in server Jscript:

   filename=/learn/rsfast_current/lib_rsfast_js.asp

<Test Script Below>


<!--include file="lib_rsfast_perf.asp"-->
<%
// fix 1: removed some extra code that wasn't executing in this version
// fix 2: fixed .add "colnames", "display" so it works correctly
String.prototype.isNull = fnIsNull;

function fnIsNull() {
    return (this.valueOf() == "" || this.valueOf() == "null" || this.valueOf() == "undefined");
}

// container for performance-related data
function CPerfData( owner ) {
    this.parent = null;    // Assigned to the CacheFast object.
    this.timeStart = 0;
    this.timeEnd = 0;
    this.timeElapsed = 0;
    this.ms = 0;
    this.sec = 0;
    this.min = 0;
    this.day = 0;
    this.params = owner.params;

    this.debugOut = Cache_DebugOut;
    this.item = Cache_GetItem;
    this.timer = Perf_Timer;
    this.show = Perf_Show;
    this.clear = Perf_ShowClear;
    this.showPretty = Perf_ShowPretty;
    this.showAll = Perf_ShowAll;
    this.keyGrab = Perf_KeyGrab;
    this.keySet = Perf_KeySet;
    this.keyIncrease = Perf_KeyIncrease;
    this.keyAddUpdate = Perf_KeyAddUpdate;
    this.makeNumber = Perf_MakeNumber;
}

// container for persistence across function calls (overcomes byref obstacle)
function CCacheFast( theparams ) {
    // properties and fields
    this.fields = new String();
    this.data = new String();
    this.asArray = new Array();    // The array representation; populated by this.toArray();
    this.params = theparams;
    this.perf = new CPerfData( this );
    this.name = new String( "" );
    this.empty = true;
    this.expired = false;

    // methods
    this.timer = Perf_Timer;    // Just here for convenience sake.
    this.item = Cache_GetItem;
    this.setItem = Cache_SetItem;
    this.run = Cache_rsFast;
    this.toArray = Cache_toArray;
    this.build = Cache_Build;
    this.grab = Cache_Grab;
    this.fetch = Cache_Fetch;
    this.show = Cache_Display;
    this.isEmpty = Cache_IsEmpty;
    this.isExpired = Cache_IsExpired;
    this.isCached = Cache_IsCached;
    this.microOptimize = Cache_MicroOptimize;
    this.debugOut = Cache_DebugOut;
}

// And the driving object...
CacheFast = new CCacheFast();

// Quick method to retrieve items from the dictionary
function Cache_GetItem( theitem ) {
    return this.params.item( theitem );
}

function Cache_SetItem( theitem,thevalue ) {
    this.params.item( theitem ) = String(thevalue).valueOf();
}

// Handles debugging output -- performs debug on/off check here.
function Cache_DebugOut( thetext ) {
    if (this.item( "debug" )) {
        rp.Write( thetext +"<br>" );
        rp.Flush();
    }
}

// The routine to start it all off.
function Cache_rsFast() {
    this.perf.timeStart = this.timer();

    // Access database build OLEDB connection string START
    sDB = String(this.item( "accdb" )).toLowerCase();

    // If they supplied a path, and it is not fully qualified, map the path
    if ((! sDB.isNull()) && (sDB.indexOf( ":" ) == -1))
        sDB = new String( Server.MapPath( sDB.valueOf() ) );
    this.setItem( "conn","PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=" +sDB +";" );

    // Caching and Data-Fetching START
    this.name = new String( this.item( "cachename" ) );
    with (this.perf) {
        ms = new String( this.item( "cachems" ) );
        sec = new String( this.item( "cachesec" ) );
        min = new String( this.item( "cachemin" ) );
        day = new String( this.item( "cacheday" ) );
    }
   
    this.setItem( "cachegrab","no" );
    this.setItem( "cachebuild","no" );
    if (! this.isCached())
        this.fetch()
    else if (Application( this.name +"_building" ))
        this.fetch()
    else {
        if (this.isExpired() || this.isEmpty())
            this.build();
        this.grab();
        this.data = this.data.split( "#r#\n" )
    }
    this.debugOut( "Calling show..." );
    this.show();

    with (this.perf) {
        timeEnd = this.timer();
        timeElapsed = timeEnd-timeStart;
    }
    this.setItem( "timetotalms",(this.perf.timeElapsed *1000) );

    // Now calculate further stats
    this.microOptimize();
    this.perf.keyAddUpdate();

    // reset cache -- should NEVER remember between calls
    this.setItem( "cachename","" );
    this.setItem( "cachemin","" );
}

function Cache_MicroOptimize() {
    // Thanks to Mike "micro-optimization" Shaffer
    this.debugOut( "Cache_MicroOptimize called" );
    this.setItem( "timeopensec",(this.item( "timeopenms" ) / 1000) );
    this.setItem( "timequerysec",(this.item( "timequeryms" ) / 1000) );
    this.setItem( "timefetchsec",(this.item( "timefetchms" ) / 1000) );
    this.setItem( "timedisplaysec",(this.item( "timedisplayms" ) / 1000) );
    this.setItem( "timetotalsec",(this.item( "timetotalms" ) / 1000) );

    this.setItem( "timeopenmin",(this.item( "timeopenms" ) / 60000) );
    this.setItem( "timequerymin",(this.item( "timequeryms" ) / 60000) );
    this.setItem( "timefetchmin",(this.item( "timefetchms" ) / 60000) );
    this.setItem( "timedisplaymin",(this.item( "timedisplayms" ) / 60000) );
    this.setItem( "timetotalmin",(this.item( "timetotalms" ) / 60000) );
}

function Cache_toArray() {
    this.debugOut( "Cache_toArray() called" );
    // Now transfer cache to Array
    this.asArray = String(Application( this.name.valueOf() +"_cachedata" )).split( "#r#\n" );
}

function Cache_Build() {
    this.debugOut( "Cache_Build called" );

    Application( this.name.valueOf() +"_building" ) = true;

    this.setItem( "cachefetch",true );
    this.fetch();
    this.setItem( "cachefetch",false );

    Application( this.name +"_cachedata" ) = this.data.valueOf();
    Application( this.name +"_cachefields" ) = this.fields.valueOf();
    // Now Expire The Cache
    // cachename_cacheExpires
    theTime = new Date().getTime();
    Application( this.name +"_cachecreated" ) = Number( theTime );
    Application( this.name +"_cachemin" ) = Number( this.item( "cachemin" ) );

    this.debugOut( "Cachecreated=" +Application( this.name +"_cachecreated" ) +"<br>" );
    this.debugOut( "cachemin=" +Application( this.name +"_cachemin" ) );

    this.setItem( "cachebuild","yes" );
    Application( this.name +"_building" ) = false;
}

function Cache_Grab() {
    this.debugOut( "Cache_Grab() called" );
   
    this.data = Application( this.name +"_cachedata" );
    this.fields = Application( this.name +"_cachefields" );
    this.setItem( "cachegrab","yes" );
}

function Cache_IsEmpty() {
    this.debugOut( "CacheEmpty called" );
    this.empty = (String(Application( this.name +"_cachedata" )).isNull());
    this.debugOut( "CacheEmpty = " +this.empty );

    return this.empty;
}

function Cache_IsExpired() {
    // If cache is out of date return TRUE
    this.debugOut( "Cache_IsExpired called" );
    cachemin = new Number( Application( this.name +"_cachemin" ) );
    if (isNaN( cachemin ))
        cachemin = 0;
    cachecreated = new Number( Application( this.name +"_cachecreated" ) );
    if (isNaN( cachecreated ))
        cachecreated = 0;
    whenexpires = cachemin +cachecreated;
    rightnow = new Date().getTime();

    this.debugOut( "cachemin: " +cachemin +"<br>cachecreated: " +cachecreated +"<br>rightnow: " +rightnow );
    this.expired = (rightnow >= whenexpires);

    this.debugOut( "expired=" +this.expired );
    return this.expired;
}
   
function Cache_IsCached() {
    // Returns True/False whether data is cache-affected
    return (! this.name.isNull());
}

function Cache_Fetch() {
    this.debugOut( "Cache_Fetch called" );

    // Open and check for EOF
    openStart = this.timer();
    var connTemp = Server.CreateObject( "ADODB.Connection" );
    connTemp.open( this.item( "conn" ) );
    openEnd = this.timer();
    openElapsed = openEnd - openStart;

    this.setItem( "timeopenms",openElapsed );

    this.debugOut( "Conn=" +this.item( "conn" ) );
    this.debugOut( "Database opened in " +openElapsed +"ms" );
    
    // If recordset is paged must be opened special
    queryStart = this.timer();
    if (Number(this.item( "pagesize" )) > 0) {
        adUseClient = 3;
        var rsTemp = Server.CreateObject( "ADODB.Recordset" );
        rsTemp.cursorLocation = adUseClient;
        rsTemp.cacheSize = this.item( "pagesize" );
        rsTemp.open( this.item( "sql" ),this.item( "conn" ) );
        rsTemp.absolutePage = this.item( "page" );

        pageMax = Number(rsTemp.pagecount);
        this.setItem( "pagemax",pagemax );
        paged = true;

        this.debugOut( "SQL = " +this.item( "sql" ) );
        this.debugOut( "Recordset opened for paging!" );
    } else
        var rsTemp = connTemp.execute( this.item( "sql" ) );

    queryEnd = this.timer();
    queryElapsed = queryEnd - queryStart;
    this.setItem( "timequeryms",openElapsed );
    this.debugOut( "SQL = " +this.item( "sql" ) );
    this.debugOut( "Query executed: " +queryElapsed +"ms" );

    fetchStart = this.timer();
    if (rsTemp.EOF) {
        this.setItem( "errordesc","No records matched" );
        this.setItem( "errornum",1 );
        this.debugOut( "EOF encountered! " +queryElapsed +"ms" );
    } else {
           // Now Fill The Array
        // Rockville#c#MD#c#20849#r#<vbcrlf>
        // Dallas#c#TX#c#XXXXX#r#<vbcrlf>
        sData = new String( rsTemp.getstring( 2,99999,"#c#","#r#\n","#n#" ) );
        this.fields = new String( "" );

        // Now Fill The FieldMaps
        // City#c#State#c#Zip#r#<vbcrlf>
        for (z=0; z < rsTemp.Fields.Count; z++)
            this.fields += rsTemp( z ).name +"#c#";

        if (this.item( "cachefetch"))
            this.data = sData;
        else
            this.data = sData.split( "#r#\n" );

        this.debugOut( "Data before split:<br>" +this.data +"<hr>" );
        this.debugOut( "Fields before split:<br>" +this.fields +"<hr>" );
        fetchElapsed = this.timer() -fetchStart;
        this.setItem( "timefetchms",fetchElapsed );
    }
    this.debugOut( "Cleaning up db objects" );

    rsTemp.close();
    rsTemp = null;
    connTemp.close();
    connTemp = null;
    this.debugOut( "Finished fetch" );
}

function Cache_Display() {
    displayStart = this.timer();
    this.debugOut( "Cache_Display called" );

    sTemplate = new String( this.item( "template" ) ).toLowerCase();
    sTemplateName = new String( this.item( "templatename" ) );
    // Templates Are Applied As Needed START
    // Probably need to replace dictionary items ONLY if they don't exist

    bTemplate = false;
    switch( sTemplate.valueOf() ) {
        case "list":
        case "listm":
            sTemplateHeader = new String( "<select name='" +sTemplateName );
            if (sTemplate.valueOf() == "listm")
                sTemplateHeader += " multiple";
            sTemplateHeader += "'>";
            sRowHeader = "<option>";
            sRowFooter = "</option>";
            sColHeader = "";
            sColFooter = "";
            sTemplateFooter = "</select><br>";
            sFieldNull = "&nbsp;";
            sFieldBlank = "&nbsp;";
            bTemplate = true;
            break;
        case "table":
        case "tablepaged":
            sTemplateHeader = new String( "<table border=1>" );
            sRowHeader = "<tr>";
            sRowFooter = "</tr>";
            sColHeader = "<td>";
            sColFooter = "</td>";
            sTemplateFooter = "</table>";
            sFieldNull = "&nbsp;";
            sFieldBlank = "&nbsp;";
            this.setItem( "colnames","display" );
            bTemplate = true;
            break;
        default:
    }

    if (! bTemplate) {
        sTemplateHeader = new String( this.item( "template_header" ) );
        sTemplateFooter = new String( this.item( "template_footer" ) );
        // Load dictionary items into simple variable to avoid
        // doing so many times in loop
        sRowHeader = this.item( "template_row_header" );
        sRowFooter = this.item( "template_row_footer" );
        sColHeader = this.item( "template_col_header" );
        sColFooter = this.item( "template_col_footer" );
        sFieldNull = this.item( "fieldnull" );
        sFieldBlank = this.item( "fieldblank" );
    }

    reg = new RegExp( "{page}","g" );
    regMax = new RegExp( "{pageMax}","g" );
    // Page x of x displays may need to appear in header/footer
    sTemplateHeader = sTemplateHeader.replace( reg,this.item( "page" ) );
    sTemplateFooter = sTemplateFooter.replace( reg,this.item( "page" ) );
    sTemplateHeader = sTemplateHeader.replace( regMax,this.item( "pagemax" ) );
    sTemplateFooter = sTemplateFooter.replace( regMax,this.item( "pagemax" ) );

    if (this.item( "debug" )) {        // Additional processing here...
        this.debugOut( "Data array=" );
        this.debugOut( "Data length=" +this.data.length +"<p>" );
        for (z=0; z < this.data.length-1; z++)
            this.debugOut( "<b>Data( " +z +" )</b> = " +this.data[z] );
        this.debugOut( "<hr>Fields: " +this.fields );
    }
   
    aFields = this.fields.split( "#c#" );
    if (this.item( "debug" )) {        // Additional processing here...
        this.debugOut( "DataFields Data" );
        this.debugOut( "DataFields length =" +aFields.length +"<p>" );
        for (z=0; z < aFields.length-1; z++)
            this.debugOut( "<b>DataFields( " +z +" )</b>=" +aFields[z] );
        this.debugOut( "<hr><b>formatting info</b>" );
        this.debugOut( "sTemplateHeader = " +Server.HTMLEncode( sTemplateHeader ) );
        this.debugOut( "sTemplateFooter = " +Server.HTMLEncode( sTemplateFooter ) );
        this.debugOut( "rowheader = " +Server.HTMLEncode( sRowHeader ) );
        this.debugOut( "rowfooter = " +Server.HTMLEncode( sRowFooter ) );
        this.debugOut( "colheader = " +Server.HTMLEncode( sColHeader ) );
        this.debugOut( "colfooter = " +Server.HTMLEncode( sColFooter ) );
        this.debugOut( "<hr>" );
        for (item in this.params)
            if (String(item).indexOf( "fld_" ) > -1)
                this.debugOut( item +" = " +Server.HTMLEncode( this.item( item ) ) );
        this.debugOut( "<hr>" );
    }

    // Page x of x displays may need to appear in header/footer
    sTemplateHeader = sTemplateHeader.replace( reg,this.item( "page" ) );
    sTemplateFooter = sTemplateFooter.replace( reg,this.item( "page" ) );
    sTemplateHeader = sTemplateHeader.replace( regMax,this.item( "pagemax" ) );
    sTemplateFooter = sTemplateFooter.replace( regMax,this.item( "pagemax" ) );

    rp.Write( sTemplateHeader );
    // Show the row header?
    if (String(this.item( "colnames" )).valueOf() == "display") {
        rp.Write( sRowHeader );
        for (c=0; c < aFields.length-1; c++)
            rp.Write( sColHeader +"<b>" +aFields[c] +"</b>" +sColFooter );
        rp.Write( sRowFooter );
    }

    iCellCount = 0;
    reg = new RegExp( "{fld}","g" );
    // suck display out of cache
    for (r=0; r < this.data.length-1; r++) {
        rp.Write( "\n" +sRowHeader );
        sRow = new String( this.data[r] ).split( "#c#" );
        for (c=0; c < aFields.length-1; c++) {
            sField = new String( aFields[c] ).toLowerCase();
            sValue = new String( sRow[c] );

            if (sValue.valueOf() == "#n#")
                sValue = new String( sFieldNull );
            if (sValue.isNull())
                sValue = new String( sFieldBlank );

            sFieldTemplate = new String( this.item( "fld_" +sField.valueOf() ) );
            if (! sFieldTemplate.isNull())
                rp.Write( sFieldTemplate.replace( reg,sValue ) )
            else
                rp.Write( "\n" +sColHeader +sValue +sColFooter +"\n" );

            iCellCount++;
        }
        rp.Write( sRowFooter +"\n" );
    }
    rp.Write( sTemplateFooter );

    displayElapsed = this.timer() -displayStart;

    this.setItem( "timedisplayms",displayElapsed );
    this.setItem( "cellcount",iCellCount );
}

function Perf_Show() {
    lineBreak = "<br>\n";
   
    rp.Write( "cachegrab=" +this.item( "cachegrab" ) +lineBreak );
    rp.Write( "cachebuild=" +this.item( "cachebuild" ) +lineBreak );
   
    rp.Write( "total ms=" +this.item( "timetotalms" ) +lineBreak );
    rp.Write( "open ms=" +this.item( "timeopenms" ) +lineBreak );
    rp.Write( "query ms=" +this.item( "timequeryms" ) +lineBreak );
    rp.Write( "fetch ms=" +this.item( "timefetchms" ) +lineBreak );
    rp.Write( "display ms=" +this.item( "timedisplayms" ) +lineBreak );
    rp.Write( "cellcount=" +this.item( "cellcount" ) +lineBreak );
    rp.Write( "<hr>" );

    rp.Write( "total sec=" +this.item( "timetotalsec" ) +lineBreak );
    rp.Write( "open sec=" +this.item( "timeopensec" ) +lineBreak );
    rp.Write( "query sec=" +this.item( "timequerysec" ) +lineBreak );
    rp.Write( "fetch sec=" +this.item( "timefetchsec" ) +lineBreak );
    rp.Write( "display sec=" +this.item( "timedisplaysec" ) +lineBreak );
    rp.Write( "<hr>" );

    rp.Write( "total min=" +this.item( "timetotalmin" ) +lineBreak );
    rp.Write( "open min=" +this.item( "timeopenmin" ) +lineBreak );
    rp.Write( "query min=" +this.item( "timequerymin" ) +lineBreak );
    rp.Write( "fetch min=" +this.item( "timefetchmin" ) +lineBreak );
    rp.Write( "display min=" +this.item( "timedisplaymin" ) +lineBreak );
    rp.Write( "<hr>" );
}

// Here is the the library file that does tracks all performance data:

function Perf_KeyAddUpdate() {
    this.debugOut( "Perf_KeyAddUpdate called" );

    // Check for FirstQuery ever
    sKeyQuery = new String( this.item( "sql" ) +"\n" +this.item( "conn" ) );
    if (String(Application( "rsfast_query_1" )).isNull()) {
        Application( "rsfast_query_1" ) = sKeyQuery.valueOf();
        Application( "rsfast_query_max" ) = 1;
        iKeyCounterMax = 1;
    }

    // See if QueryPair already exists
    iKeyCounterMax = Application( "rsfast_query_max" );
    iKeyCurrent = -1;
    for (k=0; k < iKeyCounterMax; k++)
        if (String(Application( "rsfast_query_" +k )).valueOf() == sKeyQuery.valueOf())
            iKeyCurrent = k;

    // QueryPair doesn't exist
    if (iKeyCurrent == -1) {
        Application.lock();
        Application( "rsfast_query_max" ) = Number(Application("rsfast_query_max")) +1;
        iKeyCurrent = this.makeNumber( Application( "rsfast_query_max" ) );
        Application( "rsfast_query_" +iKeyCurrent ) = sKeyQuery.valueOf();
        Application.unlock();
    }

    this.debugOut( "keyquery=" +sKeyQuery +"<br>keycurrent=" +iKeyCurrent +"<br>keycountermax=" +iKeyCounterMax );

    this.keyIncrease( iKeyCurrent,"_howmany",1 );
    this.keyIncrease( iKeyCurrent,"_totalms",this.item( "timetotalms" ) );
    this.keyIncrease( iKeyCurrent,"_openms",this.item( "timeopenms" ) );
    this.keyIncrease( iKeyCurrent,"_queryms",this.item( "timequeryms" ) );
    this.keyIncrease( iKeyCurrent,"_fetchms",this.item( "timefetchms" ) );
    this.keyIncrease( iKeyCurrent,"_displayms",this.item( "timedisplayms" ) );
    this.keyIncrease( iKeyCurrent,"_cellcount",this.item( "cellcount" ) );

    if (String(this.item( "cachegrab" )).valueOf() == "yes")
        this.keyIncrease( iKeyCurrent,"_cacheYES",1 )
    else
        this.keyIncrease( iKeyCurrent,"_cacheNO",1 );
}

function Perf_MakeNumber( num ) {
    if (isNaN( num ))
        num = 0;
    return num;
}

function Perf_KeyIncrease( key,suffix,value ) {
    // Increase This Key
    sKeyLocal = "rsfast_query_" +key +suffix;
    sKeyCount = "rsfast_query_" +key +"_howmany"
    sKeyAvg = sKeyLocal +"_avg";

    this.debugOut( "keylocal=" +sKeyLocal +"<br>keycount=" +sKeyCount +"<br>keyavg=" +sKeyAvg );

    Application.lock();
    Application( sKeyLocal.valueOf() ) = Number(this.makeNumber(Application( sKeyLocal.valueOf() )) +this.makeNumber(value));
    Application.unlock();
    // Average This Key
    Application( sKeyAvg.valueOf() ) = Number(this.makeNumber(Application( sKeyLocal.valueOf() )) / this.makeNumber(Application( sKeyCount )));
}

function Perf_KeySet( key,suffix,value ) {
    Application( "rsfast_" +key +suffix ) = value;
}

function Perf_KeyGrab( key,suffix ) {
    sKey = "rsfast_query_" +key +suffix;
    return Application( sKey.valueOf() );
}

function Perf_ShowAll() {
    for (item in Application.contents)
        if (String(item).indexOf( "rsfast" ) != -1)
            rp.Write( "<b>" +item +"=</b>" +Application.contents( item ) +"<br>" );
}

function Perf_ShowPretty() {
    sPerfPad = "&nbsp;&nbsp;&nbsp;";
    sPerfSep = "<br><hr><br>";
    sPerfLB = "<br>\n";
   
    iKeyCounterMax = Number(Application( "rsfast_query_max" ));
    for (k=0; k < iKeyCounterMax; k++) {
        rp.Write( "Query #" +k +"<br>" );
        rp.Write( sPerfPad +"Query: <b>" );
        rp.Write( this.keyGrab( k,"") +"</b>" +sPerfLB );
        rp.Write( sPerfPad +"Query Count: <b>" );
        rp.Write( this.keyGrab( k,"_howmany") +"</b>" +sPerfLB );
        rp.Write( sPerfPad +"Query Total ms ave.: <b>" );
        rp.Write( this.keyGrab( k,"_totalms_avg") +"</b>" +sPerfLB );
        rp.Write( sPerfPad +"Cached YES: <b>" );
        rp.Write( this.keyGrab( k,"_cacheYES") +"</b>" +sPerfLB );
        rp.Write( sPerfPad +"Cached NO: <b>" );
        rp.Write( this.keyGrab( k,"_cacheNO") +"</b>" +sPerfLB );
    }
}

function Perf_ShowClear() {
    for (item in Application.contents)
        if (String(item).indexOf( "rsfast" ) != -1) {
            rp.Write( "<b>Killing" +item +"<br>" );
            Application.contents.remove( item );
        } else
            rp.Write( "<b>" +item +"</b><br>" );
}

function Perf_Timer() {
    return (new Date().getTime()) / 1000;
}
%>

Chaz Wish List
Tall Tip $5
Grande Tip $20
Venti Tip $39
Tip Jar Thanks
2004 Thanks
2005 Thanks
HUGE Tip -love site