/*
 * Process Hacker Plugins -
 *   Performance Monitor Plugin
 *
 * Copyright (C) 2016 dmex
 *
 * This file is part of Process Hacker.
 *
 * Process Hacker is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Process Hacker is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Process Hacker.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "perfmon.h"

VOID PerfMonEntryDeleteProcedure(
    _In_ PVOID Object,
    _In_ ULONG Flags
    )
{
    PPERF_COUNTER_ENTRY entry = Object;
        
    PhAcquireQueuedLockExclusive(&PerfCounterListLock);
    PhRemoveItemList(PerfCounterList, PhFindItemList(PerfCounterList, entry));
    PhReleaseQueuedLockExclusive(&PerfCounterListLock);

    DeletePerfCounterId(&entry->Id);
    PhDeleteCircularBuffer_ULONG64(&entry->HistoryBuffer);

    if (entry->PerfQueryHandle)
    {
        PdhCloseQuery(entry->PerfQueryHandle);
        entry->PerfQueryHandle = NULL;
    }

    if (entry->PerfCounterInfo)
    {
        PhFree(entry->PerfCounterInfo);
        entry->PerfCounterInfo = NULL;
    }

    //PhDereferenceObject(entry);
}

VOID PerfMonInitialize(
    VOID
    )
{
    PerfCounterList = PhCreateList(1);
    PerfCounterEntryType = PhCreateObjectType(L"PerfMonEntry", 0, PerfMonEntryDeleteProcedure);
}

VOID PerfMonUpdate(
    VOID
    )
{
    static ULONG runCount = 0; // MUST keep in sync with runCount in process provider

    PhAcquireQueuedLockShared(&PerfCounterListLock);

    for (ULONG i = 0; i < PerfCounterList->Count; i++)
    {
        PPERF_COUNTER_ENTRY entry;
        ULONG counterType = 0;
        PDH_FMT_COUNTERVALUE displayValue = { 0 };

        entry = PhReferenceObjectSafe(PerfCounterList->Items[i]);

        if (!entry)
            continue;

        if (entry->HaveFirstSample)
        {
            if (!entry->PerfQueryHandle)
            {               
                if (PdhOpenQuery(NULL, 0, &entry->PerfQueryHandle) == ERROR_SUCCESS)
                {
                    if (PdhAddCounter(entry->PerfQueryHandle, PhGetString(entry->Id.PerfCounterPath), 0, &entry->PerfCounterHandle) == ERROR_SUCCESS)
                    {
                        ULONG counterLength;

                        if (PdhGetCounterInfo(entry->PerfCounterHandle, TRUE, &counterLength, NULL) == PDH_MORE_DATA)
                        {
                            entry->PerfCounterInfo = PhAllocate(counterLength);
                            memset(entry->PerfCounterInfo, 0, counterLength);
                        }

                        if (PdhGetCounterInfo(entry->PerfCounterHandle, TRUE, &counterLength, entry->PerfCounterInfo) == ERROR_SUCCESS)
                        {
                            
                        }

                        OutputDebugString(L"");
                    }
                }
            }

            // Update the counter data
            PdhCollectQueryData(entry->PerfQueryHandle);

            //PdhSetCounterScaleFactor(entry->PerfCounterHandle, PDH_MAX_SCALE);
            //PdhGetRawCounterValue(entry->PerfCounterHandle, 0, &rawValue);

            if (PdhGetFormattedCounterValue(
                entry->PerfCounterHandle,
                PDH_FMT_LARGE | PDH_FMT_NOSCALE | PDH_FMT_NOCAP100,
                &counterType,
                &displayValue
                ) == ERROR_SUCCESS && counterType == PERF_COUNTER_COUNTER)
            {
                PhUpdateDelta(&entry->HistoryDelta, displayValue.largeValue);
            }
        }
        else
        {
            PhInitializeDelta(&entry->HistoryDelta); // The first sample must be zero

            entry->HaveFirstSample = TRUE;
        }

        if (runCount != 0)
        {
            PhAddItemCircularBuffer_ULONG64(&entry->HistoryBuffer, entry->HistoryDelta.Value);
        }

        PhDereferenceObjectDeferDelete(entry);
    }

    PhReleaseQueuedLockShared(&PerfCounterListLock);

    runCount++;
}

VOID InitializePerfCounterId(
    _Out_ PPERF_COUNTER_ID Id,
    _In_ PPH_STRING PerfCounterPath
    )
{
    PhSetReference(&Id->PerfCounterPath, PerfCounterPath);
}

VOID CopyPerfCounterId(
    _Out_ PPERF_COUNTER_ID Destination,
    _In_ PPERF_COUNTER_ID Source
    )
{
    InitializePerfCounterId(Destination, Source->PerfCounterPath);
}

VOID DeletePerfCounterId(
    _Inout_ PPERF_COUNTER_ID Id
    )
{
    PhClearReference(&Id->PerfCounterPath);
}

BOOLEAN EquivalentPerfCounterId(
    _In_ PPERF_COUNTER_ID Id1,
    _In_ PPERF_COUNTER_ID Id2
    )
{
    if (!Id1 || !Id2)
        return FALSE;

    return PhEqualString(Id1->PerfCounterPath, Id2->PerfCounterPath, TRUE);
}

PPERF_COUNTER_ENTRY CreatePerfCounterEntry(
    _In_ PPERF_COUNTER_ID Id
    )
{
    PPERF_COUNTER_ENTRY entry;

    entry = PhCreateObject(sizeof(PERF_COUNTER_ENTRY), PerfCounterEntryType);
    memset(entry, 0, sizeof(PERF_COUNTER_ENTRY));

    CopyPerfCounterId(&entry->Id, Id);

    PhInitializeCircularBuffer_ULONG64(&entry->HistoryBuffer, PhGetIntegerSetting(L"SampleCount"));

    PhAcquireQueuedLockExclusive(&PerfCounterListLock);
    PhAddItemList(PerfCounterList, entry);
    PhReleaseQueuedLockExclusive(&PerfCounterListLock);

    return entry;
}