/*++

Copyright (c) 1995-1999  Microsoft Corporation

Module Name:

    heapstat.cxx

Abstract:

    This module contains an NTSD debugger extension for dumping various
    heap statistics.

Author:

    Keith Moore (keithmo) 01-Nov-1997

Revision History:

--*/

#include "inetdbgp.h"


#define MAX_SIZE    65536

// Large busy block (size exceeds MAX_SIZE)
#define MAX_LBBSIZE 1024


typedef struct _ENUM_CONTEXT {

    BOOLEAN ContinueEnum;
    ULONG FreeJumbo;
    ULONG BusyJumbo;
    ULONG FreeJumboBytes;
    ULONG BusyJumboBytes;
    ULONG BusyOverhead;
    ULONG FreeOverhead;
    ULONG FreeCounters[MAX_SIZE];
    ULONG BusyCounters[MAX_SIZE];
    ULONG LargeBusyBlock[MAX_LBBSIZE];

} ENUM_CONTEXT, *PENUM_CONTEXT;

#define BYTES_TO_K(cb) ( ( (cb) + 512 ) / 1024 )


/************************************************************
 * Dump Heap Info
 ************************************************************/


BOOLEAN
CALLBACK
HspEnumHeapSegmentEntriesProc(
    IN PVOID Param,
    IN PHEAP_ENTRY LocalHeapEntry,
    IN PHEAP_ENTRY RemoteHeapEntry
    )

/*++

Routine Description:

    Callback invoked for each heap entry within a heap segment.

Arguments:

    Param - An uninterpreted parameter passed to the enumerator.

    LocalHeapEntry - Pointer to a local copy of the HEAP_ENTRY structure.

    RemoteHeapEntry - The remote address of the HEAP_ENTRY structure
        in the debugee.

Return Value:

    BOOLEAN - TRUE if the enumeration should continue, FALSE if it
        should be terminated.

--*/

{

    PENUM_CONTEXT context = (PENUM_CONTEXT)Param;
    ULONG entryLength;
    ULONG allocLength;

    //
    // Calculate the total length of this entry, including the heap
    // header and any "slop" at the end of the block.
    //

    entryLength = (ULONG)LocalHeapEntry->Size << HEAP_GRANULARITY_SHIFT;

    //
    // From that, compute the number of bytes in use. This is the size
    // of the allocation request as received from the application.
    //

    allocLength = entryLength - (ULONG)LocalHeapEntry->UnusedBytes;

    //
    // Adjust the appropriate accumulators.
    //

    if( LocalHeapEntry->Flags & HEAP_ENTRY_BUSY ) {

        context->BusyOverhead += entryLength;

        if( allocLength < MAX_SIZE ) {
            context->BusyCounters[allocLength] += 1;
        } else {
            context->BusyJumbo += 1;
            context->BusyJumboBytes += allocLength;

            if (context->LargeBusyBlock[MAX_LBBSIZE-1] == 0) {
                BOOL fFound = FALSE;
                UINT  i = 0;
                for (; context->LargeBusyBlock[i] != 0 && i < MAX_LBBSIZE; i++) {
                    if( CheckControlC() ) {
                        context->ContinueEnum = FALSE;
                        return FALSE;
                    }

                    if (allocLength == context->LargeBusyBlock[i]) {
                        fFound = TRUE;  
                        break;
                    }
                }
                if (!fFound && i < MAX_LBBSIZE-1) {
                   context->LargeBusyBlock[i] = allocLength; 
                }
            }
        }

    } else {

        context->FreeOverhead += entryLength;

        if( allocLength < MAX_SIZE ) {
            context->FreeCounters[allocLength] += 1;
        } else {
            context->FreeJumbo += 1;
            context->FreeJumboBytes += allocLength;
        }

    }

    return TRUE;

}   // HspEnumHeapSegmentEntriesProc


BOOLEAN
CALLBACK
HspEnumHeapSegmentsProc(
    IN PVOID Param,
    IN PHEAP_SEGMENT LocalHeapSegment,
    IN PHEAP_SEGMENT RemoteHeapSegment,
    IN ULONG HeapSegmentIndex
    )

/*++

Routine Description:

    Callback invoked for each heap segment within a heap.

Arguments:

    Param - An uninterpreted parameter passed to the enumerator.

    LocalHeapSegment - Pointer to a local copy of the HEAP_SEGMENT
        structure.

    RemoteHeapSegment - The remote address of the HEAP_SEGMENT
        structure in the debugee.

Return Value:

    BOOLEAN - TRUE if the enumeration should continue, FALSE if it
        should be terminated.

--*/

{

    //
    // Enumerate the entries for the specified segment.
    //

    if( !EnumHeapSegmentEntries(
            LocalHeapSegment,
            RemoteHeapSegment,
            HspEnumHeapSegmentEntriesProc,
            Param
            ) ) {
        dprintf( "error retrieving heap segment entries\n" );
        return FALSE;
    }

    return TRUE;

}   // HspEnumHeapSegmentsProc


BOOLEAN
CALLBACK
HspEnumHeapsProc(
    IN PVOID Param,
    IN PHEAP LocalHeap,
    IN PHEAP RemoteHeap,
    IN ULONG HeapIndex
    )

/*++

Routine Description:

    Callback invoked for each heap within a process.

Arguments:

    Param - An uninterpreted parameter passed to the enumerator.

    LocalHeap - Pointer to a local copy of the HEAP structure.

    RemoteHeap - The remote address of the HEAP structure in the debugee.

Return Value:

    BOOLEAN - TRUE if the enumeration should continue, FALSE if it
        should be terminated.

--*/

{

    //
    // Enumerate the segments for the specified heap.
    //

    if( !EnumHeapSegments(
            LocalHeap,
            RemoteHeap,
            HspEnumHeapSegmentsProc,
            Param
            ) ) {
        dprintf( "error retrieving heap segments\n" );
        return FALSE;
    }

    return TRUE;

}   // HspEnumHeapsProc


DECLARE_API( heapstat )

/*++

Routine Description:

    This function is called as an NTSD extension to format and dump
    heap statistics.

Arguments:

    hCurrentProcess - Supplies a handle to the current process (at the
        time the extension was called).

    hCurrentThread - Supplies a handle to the current thread (at the
        time the extension was called).

    CurrentPc - Supplies the current pc at the time the extension is
        called.

    lpExtensionApis - Supplies the address of the functions callable
        by this extension.

    lpArgumentString - Supplies the asciiz string that describes the
        ansi string to be dumped.

Return Value:

    None.

--*/

{

    PENUM_CONTEXT context;
    ULONG i;
    ULONG busyBytes;
    ULONG totalBusy;
    ULONG totalFree;
    ULONG totalBusyBytes;
    ULONG lowerNoiseBound;

    INIT_API();

    //
    // Setup.
    //

    context = (PENUM_CONTEXT)malloc( sizeof(*context) );

    if( context == NULL ) {
        dprintf( "out of memory\n" );
        return;
    }

    RtlZeroMemory(
        context,
        sizeof(*context)
        );

    context->ContinueEnum = TRUE;

    //
    // Skip leading blanks.
    //

    while( *lpArgumentString == ' ' ||
           *lpArgumentString == '\t' ) {
        lpArgumentString++;
    }

    if( *lpArgumentString == '\0' ) {
        lowerNoiseBound = 1;
    } else {
        lowerNoiseBound = strtoul( lpArgumentString, NULL, 16 );
    }

    //
    // Enumerate the heaps, which will enumerate the segments, which
    // will enumerate the entries, which will accumulate the statistics.
    //

    if( !EnumProcessHeaps(
            HspEnumHeapsProc,
            (PVOID)context
            ) ) {
        dprintf( "error retrieving process heaps\n" );
        free( context );
        return;
    }

    //
    // Dump 'em.
    //

    dprintf(
        "  Size :  NumBusy :  NumFree : BusyBytes\n"
        );

    totalBusy = 0;
    totalFree = 0;
    totalBusyBytes = 0;

    for( i = 0 ; i < MAX_SIZE ; i++ ) {

        if (CheckControlC())
            goto cleanup;

        busyBytes = i * context->BusyCounters[i];

        if( context->BusyCounters[i] >= lowerNoiseBound ||
            context->FreeCounters[i] >= lowerNoiseBound ) {

            dprintf(
                " %5lx : %8lx : %8lx :  %8lx (%10ldK)\n",
                i,
                context->BusyCounters[i],
                context->FreeCounters[i],
                busyBytes,
                BYTES_TO_K( busyBytes )
                );

        }

        totalBusy += context->BusyCounters[i];
        totalBusyBytes += busyBytes;
        totalFree += context->FreeCounters[i];

    }

    if( context->BusyJumbo >= lowerNoiseBound ||
        context->FreeJumbo >= lowerNoiseBound ) {

        dprintf(
            ">%5lx : %8lx : %8lx :  %8lx (%10ldK)\n",
            MAX_SIZE,
            context->BusyJumbo,
            context->FreeJumbo,
            context->BusyJumboBytes,
            BYTES_TO_K( context->BusyJumboBytes )
            );

        totalBusy += context->BusyJumbo;
        totalFree += context->FreeJumbo;
        totalBusyBytes += context->BusyJumboBytes;

    }

    if (context->LargeBusyBlock[0] != 0) {
        for (i = 0; i < MAX_LBBSIZE && context->LargeBusyBlock[i] != 0; i++) {
            dprintf("%8lx : \n", context->LargeBusyBlock[i]);
        }
    }

    dprintf(
        " Total : %8lx : %8lx :  %8lx (%10ldK)\n"
        "\n"
        " Total Heap Impact from Busy Blocks = %8lx (%10ldK)\n"
        " Total Heap Impact from Free Blocks = %8lx (%10ldK)\n",
        totalBusy,
        totalFree,
        totalBusyBytes,
        BYTES_TO_K( totalBusyBytes ),
        context->BusyOverhead,
        BYTES_TO_K( context->BusyOverhead ),
        context->FreeOverhead,
        BYTES_TO_K( context->FreeOverhead )
        );

  cleanup:
    free( context );

}   // DECLARE_API( heapstat )

