/*++

Copyright (c) Microsoft Corporation. All rights reserved. 

You may only use this code if you agree to the terms of the Windows Research Kernel Source Code License agreement (see License.txt).
If you do not agree to the terms, do not use the code.


Module Name:

    callperf.c

Abstract:

   This module implements the functions necessary to collect call data.

--*/

#include "exp.h"

VOID
ExInitializeCallData (
    IN PCALL_PERFORMANCE_DATA CallData
    )

/*++

Routine Description:

    This function initializes a call performance data structure.

Arguments:

    CallData - Supplies a pointer to the call performance data structure
        that is initialized.

Return Value:

    None.

--*/

{

    ULONG Index;

    //
    // Initialize the spinlock and listheads for the call performance
    // data structure.
    //

    KeInitializeSpinLock(&CallData->SpinLock);
    for (Index = 0; Index < CALL_HASH_TABLE_SIZE; Index += 1) {
        InitializeListHead(&CallData->HashTable[Index]);
    }
}

VOID
ExRecordCallerInHashTable (
    IN PCALL_PERFORMANCE_DATA CallData,
    IN PVOID CallersAddress,
    IN PVOID CallersCaller
    )

/*++

Routine Description:

    This function records call data in the specified call performance
    data structure.

Arguments:

    CallData - Supplies a pointer to the call performance data structure
        in which the call data is recorded.

    CallersAddress - Supplies the address of the caller of a function.

    CallersCaller - Supplies the address of the caller of a caller of
        a function.

Return Value:

    None.

--*/

{

    PCALL_HASH_ENTRY HashEntry;
    ULONG Hash;
    PCALL_HASH_ENTRY MatchEntry;
    PLIST_ENTRY NextEntry;
    KIRQL OldIrql;

    //
    // If the initialization phase is not zero, then collect call performance
    // data.
    //

    if (InitializationPhase != 0) {

        //
        // Acquire the call performance data structure spinlock.
        //

        ExAcquireSpinLock(&CallData->SpinLock, &OldIrql);

        //
        // Lookup the callers address in call performance data hash table. If
        // the address does not exist in the table, then create a new entry.
        //

        Hash = (ULONG)((ULONG_PTR)CallersAddress ^ (ULONG_PTR)CallersCaller);
        Hash = ((Hash >> 24) ^ (Hash >> 16) ^ (Hash >> 8) ^ (Hash)) & (CALL_HASH_TABLE_SIZE - 1);
        MatchEntry = NULL;
        NextEntry = CallData->HashTable[Hash].Flink;
        while (NextEntry != &CallData->HashTable[Hash]) {
            HashEntry = CONTAINING_RECORD(NextEntry,
                                          CALL_HASH_ENTRY,
                                          ListEntry);

            if ((HashEntry->CallersAddress == CallersAddress) &&
                (HashEntry->CallersCaller == CallersCaller)) {
                MatchEntry = HashEntry;
                break;
            }

            NextEntry = NextEntry->Flink;
        }

        //
        // If a matching caller address was found, then update the call site
        // statistics. Otherwise, allocate a new hash entry and initialize
        // call site statistics.
        //

        if (MatchEntry != NULL) {
            MatchEntry->CallCount += 1;

        } else {
            MatchEntry = ExAllocatePoolWithTag(NonPagedPool,
                                              sizeof(CALL_HASH_ENTRY),
                                              'CdHe');

            if (MatchEntry != NULL) {
                MatchEntry->CallersAddress = CallersAddress;
                MatchEntry->CallersCaller = CallersCaller;
                MatchEntry->CallCount = 1;
                InsertTailList(&CallData->HashTable[Hash],
                               &MatchEntry->ListEntry);
            }
        }

        //
        // Release the call performance data structure spinlock.
        //

        ExReleaseSpinLock(&CallData->SpinLock, OldIrql);
    }

    return;
}

