Monitoring Web Application Performance with ISAPI Aids      
by Kevin T. Manley


Listing One
// Handler for SF_NOTIFY_URL_MAP notification
DWORD OnNotifyUrlMap(
    PHTTP_FILTER_CONTEXT pfc,  
    HTTP_FILTER_URL_MAP* pUrlMap
)
{
    // If this is an internal request, handle it now
    if( strstr( pUrlMap->pszURL, "monitor-isapi-filter" ) ) {
        return OnInternalRequest( pfc, pUrlMap ); 
    } else if( !bOn ) { 
        // Otherwise, if turned off, disable notifications & exit
        DisableNotifications(pfc); 
        return SF_STATUS_REQ_NEXT_NOTIFICATION;
    } // if
      // Get context data structure for this request
   ContextInfo* pContextInfo=contextMap.GetOrCreateContextInfo( (DWORD) pfc );
   if( pContextInfo ) {
        // No need to lock pContextInfo; it can only be active in 
        // one invocation of HttpFilterProc at a time
        pContextInfo->start = StartTiming(); 
        pContextInfo->bytes = 0; 
    } else {
      TRACE0("[MIF] INFO: failed to create context info in OnNotifyUrlMap\n");
    } // else
    return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

Listing Two
// Fetches ContextInfo pointer from list, assigns a new 
// slot for it if it doesn't already exist
inline ContextInfo* ContextMap::GetOrCreateContextInfo( 
    DWORD dwKey 
) 
{
    int slot=0; 
    CritSectLock lock(&cs); // lock list
    ContextInfo* pInfo = internalGetContextInfo( dwKey, &slot );
    if( (pInfo == NULL) && slot>0 ) {
        // We didn't find it, but there is a slot available
        pInfo = &(info[slot]);
        pInfo->Clear();
        pInfo->key = dwKey;
    } // if
    return pInfo;
}
// Fetches ContextInfo pointer from map if available. While scanning list, 
// keep track if you've found any empty slots. If return value is NULL,
// then pnAvailSlot may point to a nonzero available slot number if a slot was
// available. NOTE: caller must synchronize access to the info data structure
inline ContextInfo* ContextMap::internalGetContextInfo( 
    DWORD dwKey, 
    int* pnAvailSlot 
) 
{
    for( int i=0; i<MAX_CONCURRENT_REQUESTS; i++ ) {
        ContextInfo* pInfo = &(info[i]); 
        if( pInfo->key == dwKey ) {
            return pInfo; 
        } else if( pInfo->key == 0 ) {
            *pnAvailSlot = i; 
        } // else
    } // for
    return NULL;
}

Listing Three
// Handler for SF_NOTIFY_END_OF_REQUEST notification
DWORD OnEndOfRequest(
    PHTTP_FILTER_CONTEXT pfc  
)
{
    // Get context data structure for this request
    ContextInfo* pContextInfo = contextMap.GetContextInfo( (DWORD) pfc );
    if( pContextInfo ) {
        // NOTE: no need to lock pContextInfo since it can only 
        // be active in one invocation of HttpFilterProc at a time
        // Get URL of request
        char szPageName[MAX_URL_LEN];
        GetRequestUrl( pfc, szPageName, MAX_URL_LEN );  

        // Get or create data structure for this page
        PageInfo* pPageInfo = pageMap.GetOrCreatePageInfo( szPageName );
        if( pPageInfo ) {
            CritSectLock lock(&(pPageInfo->cs)); // lock PageInfo structure
            DWORD dwDuration = (DWORD) EndTiming( pContextInfo->start ); 
            // If duration is truncated to 0, bump it to min resolution of 1ms
            if( dwDuration == 0 ) {
                dwDuration = 1;
            } // if
            DWORD dwBytes = pContextInfo->bytes;

            pPageInfo->numcalls++;
            pPageInfo->duration.Update(dwDuration);
            pPageInfo->bytes.Update(dwBytes);
            // Make copies of pPageInfo data so we can exit critsect before
            // expensive sprintf/trace calls
            long numcalls = pPageInfo->numcalls;
            Counter duration = pPageInfo->duration;
            Counter bytes = pPageInfo->bytes;
            // Release page and context info, unblocking waiting threads 
            lock.Unlock();
            contextMap.FreeContextInfo(pContextInfo);

            TRACE10( "[MIF] %s: calls=%lu, time=%lu (min=%lu, max=%lu, 
               avg=%lu), bytes=%lu (min=%lu, max=%lu, avg=%lu)\n", szPageName,
               numcalls, dwDuration, duration.dMin, duration.dMax, 
               duration.Average(numcalls), dwBytes, bytes.dMin, bytes.dMax, 
               bytes.Average(numcalls));
        } else {
            TRACE0( "[MIF] INFO: failed to get or create 
                                        page info in OnEndOfRequest\n" );
        } // else
    } else {
        TRACE0("[MIF] INFO: failed to get context info in OnEndOfRequest\n");
    } // else
    return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

Listing Four
// Handler for SF_NOTIFY_SEND_RESPONSE
DWORD OnSendResponse( 
    PHTTP_FILTER_CONTEXT pfc,  
    HTTP_FILTER_SEND_RESPONSE* pfsr
)
{
    // The monitor SF_NOTIFY_SEND_RESPONSE to avoid a possible DoS attack
    // Since we allocate memory on each unique URI served, malicious attackers
    // could hit server with random URIs, filling our memory. Therefore if we
    // see that this request resulted in a 404 Not Found error, we skip 
    // logging for this request
    if( pfsr->HttpStatus == 404 ) {
        ContextInfo* pContextInfo = contextMap.GetContextInfo( (DWORD) pfc );
        if( pContextInfo ) {
            contextMap.FreeContextInfo( pContextInfo ); 
        } // if
        DisableNotifications( pfc ); 
    } // if
    return SF_STATUS_REQ_NEXT_NOTIFICATION;
}


Listing Five
// Dumps profile data in tab-delimited format to the browser.
DWORD DumpProfileData(
    PHTTP_FILTER_CONTEXT pfc  
)
{
  LPCSTR FIRSTLINE = 
  "PageName\tNumCalls\tMinDuration\tMaxDuration\tAccumDuration\tAvgDuration\t"
  "MinBytes\tMaxBytes\tAccumBytes\tAvgBytes\tAvgBytesPerSec\n";
  LPCSTR HEADERS =    "HTTP/1.0 200 OK\r\n"
                      "Content-Type: application/vnd.ms-excel\r\n"
                      "Cache-Control: no-cache\r\n"
                      "Expires: Mon, 09 Oct 2000 16:00:00 GMT\r\n" 
                      "Pragma: no-cache\r\n"
                      "\r\n";
    DWORD dwLen = strlen(HEADERS);
    pfc->WriteClient( pfc, (LPVOID)HEADERS, &dwLen, 0); 

    dwLen = strlen(FIRSTLINE);
    pfc->WriteClient( pfc, (LPVOID)FIRSTLINE, &dwLen, 0);

    strstream stream;
    pageMap.Dump(stream); 

    dwLen = stream.pcount();
    pfc->WriteClient( pfc, stream.str(), &dwLen, 0);

    return SF_STATUS_REQ_FINISHED;
}




3


