Verwenden der PerfLib-Funktionen zum Verarbeiten von Leistungsindikatordaten

Verwenden Sie die PerfLib-Consumerfunktionen, um Leistungsdaten von V2-Leistungsdatenanbietern zu nutzen, wenn Sie die PDH-Funktionen (Performance Data Helper)nicht verwenden können. Diese Funktionen können verwendet werden, wenn Sie OneCore Anwendungen schreiben, um V2-Countersets zu erfassen, oder wenn Sie V2-Indikatorensets mit minimalen Abhängigkeiten und minimalem Mehraufwand erfassen müssen.

Tipp

Die PerfLib V2-Consumerfunktionen sind schwieriger zu verwenden als die PDH-Funktionen (Performance Data Helper) und unterstützen nur das Sammeln von Daten von V2-Anbietern. Die PDH-Funktionen sollten für die meisten Anwendungen bevorzugt werden.

Die PerfLib V2-Consumerfunktionen sind die Low-Level-API zum Sammeln von Daten von V2-Anbietern. Die PerfLib V2-Consumerfunktionen unterstützen nicht das Sammeln von Daten von V1-Anbietern.

Warnung

Die PerfLib V2-Consumerfunktionen können möglicherweise Daten aus nicht vertrauenswürdigen Quellen sammeln, z. B. von lokalen Diensten mit eingeschränkten Berechtigungen oder von Remotecomputern. Die PerfLib V2-Consumerfunktionen überprüfen die Daten nicht auf Integrität oder Konsistenz. Ihre Consumeranwendung muss überprüfen, ob die zurückgegebenen Daten konsistent sind, z. B. dass die Werte size im zurückgegebenen Datenblock die tatsächliche Größe des zurückgegebenen Datenblocks nicht überschreiten. Dies ist besonders wichtig, wenn die Consumeranwendung mit erhöhten Rechten ausgeführt wird.

PerfLib-Nutzung

Der perflib.h Header enthält die Deklarationen, die von V2-Benutzermodusanbietern (d.h. der PerfLib-Anbieter-API) und V2-Consumern (d.h. der PerfLib-Consumer-API) verwendet werden. Sie deklariert die folgenden Funktionen zum Nutzen von V2-Leistungsdaten:

  • Verwenden Sie PerfEnumerateCounterSet, um die GUIDs der Countersets abzurufen, die von den V2-Anbietern im System registriert werden.
  • Verwenden Sie PerfQueryCounterSetRegistrationInfo, um Informationen zu einem bestimmten Counterset abzurufen, z. B. den Namen des Countersets, eine Beschreibung, einen Typ (einzelinstanzenweise oder mehrere Instanzen), Indikatortypen, Indikatornamen und Indikatorbeschreibungen.
  • Verwenden Sie PerfEnumerateCounterSetInstances, um die Namen der derzeit aktiven Instanzen eines Countersets mit mehreren Instanzen abzurufen.
  • Verwenden Sie PerfOpenQueryHandle, um ein neues Abfragehandle zu erstellen, das zum Sammeln von Daten aus einem oder mehreren Countersets verwendet werden soll.
  • Verwenden Sie PerfCloseQueryHandle, um ein Abfragehandle zu schließen.
  • Verwenden Sie PerfAddCounters, um einem Abfragehandle eine Abfrage hinzuzufügen.
  • Verwenden Sie PerfDeleteCounters, um eine Abfrage aus einem Abfragehandle zu entfernen.
  • Verwenden Sie PerfQueryCounterInfo, um Informationen zu den aktuellen Abfragen für ein Abfragehandle abzurufen.
  • Verwenden Sie PerfQueryCounterData, um die Leistungsdaten aus einem Abfragehandle zu sammeln.

Wenn Ihr Consumer Nur Daten von einem bestimmten Anbieter verwendet, bei dem die GUID und die Indikatorindizes stabil sind und Sie Zugriff auf die von CTRPPgenerierte Symboldatei (über den CTRPP-Parameter) -ch haben, können Sie die Counterset-GUID und die Indikatorindexwerte in Ihren Consumer hart codieren.

Andernfalls müssen Sie Countersetmetadaten laden, um die Counterset-GUID(s) und Indikatorindizes zu bestimmen, die in Ihrer Abfrage verwendet werden sollen:

  • Verwenden Sie PerfEnumerateCounterSet, um eine Liste der unterstützten Counterset-GUIDs abzurufen.
  • Verwenden Sie für jede GUID PerfQueryCounterSetRegistrationInfo, um den Countersetnamen zu laden. Halten Sie an, wenn Sie den gewünschten Namen finden.
  • Verwenden Sie PerfQueryCounterSetRegistrationInfo, um die verbleibenden Metadaten (Countersetkonfiguration, Indikatornamen, Indikatorindizes, Indikatortypen) für dieses Counterset zu laden.

Wenn Sie nur die Namen der derzeit aktiven Instanzen eines Countersets kennen müssen (d. h. wenn Sie die tatsächlichen Leistungsdatenwerte nicht benötigen), können Sie PerfEnumerateCounterSetInstances verwenden. Dies nimmt eine Counterset-GUID als Eingabe an und gibt einen PERF_INSTANCE_HEADER Block mit den Namen und IDs der derzeit aktiven Instanzen des angegebenen Countersets zurück.

Abfragen

Zum Sammeln von Leistungsdaten müssen Sie ein Abfragehandle erstellen, Abfragen hinzufügen und die Daten aus den Abfragen sammeln.

Einem Abfragehandle können viele Abfragen zugeordnet sein. Wenn Sie PerfQueryCounterData für ein Abfragehandle aufrufen, führt PerfLib alle Abfragen des Handles aus und erfasst alle Ergebnisse.

Jede Abfrage gibt eine Counterset-GUID, einen Instanznamenfilter, einen optionalen Instanz-ID-Filter und einen optionalen Indikator-ID-Filter an.

  • Die Counterset-GUID ist erforderlich. Sie gibt die GUID des Countersets an, aus dem die Abfrage Daten sammelt. Dies ähnelt der FROM -Klausel einer SQL Abfrage.
  • Der Instanznamenfilter ist erforderlich. Es gibt ein Platzhaltermuster an, mit dem der Instanzname übereinstimmen muss, damit die Instanz in die Abfrage eingeschlossen werden kann, wobei * "beliebige Zeichen" und ? "ein Zeichen" angegeben wird. Für Einzelinstanz-Indikatorensets MUSS diese auf eine Zeichenfolge der Länge 0 (null) festgelegt "" werden. Für Indikatorensets mit mehreren Instanzen MUSS diese auf eine nicht leere Zeichenfolge festgelegt werden (verwenden Sie "*" , um alle Instanznamen zu akzeptieren). Dies ähnelt einer WHERE InstanceName LIKE NameFilter -Klausel einer SQL Abfrage.
  • Der Instanz-ID-Filter ist optional. Wenn vorhanden (d. h. wenn auf einen anderen Wert als festgelegt 0xFFFFFFFF ist), gibt sie an, dass die Abfrage nur Instanzen sammeln soll, bei denen die Instanz-ID mit der angegebenen ID übereinstimmt. Wenn sie nicht vorhanden ist (d. h. wenn sie auf festgelegt 0xFFFFFFFF ist), gibt sie an, dass die Abfrage alle Instanz-IDs akzeptieren soll. Dies ähnelt einer WHERE InstanceId == IdFilter -Klausel einer SQL Abfrage.
  • Der Indikator-ID-Filter ist optional. Wenn vorhanden (d. h. wenn auf einen anderen Wert als festgelegt), PERF_WILDCARD_COUNTER gibt dies an, dass die Abfrage einen einzelnen Zähler sammeln soll, ähnlich wie eine SELECT CounterName -Klausel einer SQL Abfrage. Wenn nicht vorhanden (d. h. wenn auf PERF_WILDCARD_COUNTER festgelegt), gibt dies an, dass die Abfrage alle verfügbaren Leistungsindikatoren erfassen soll, ähnlich wie eine SELECT * -Klausel einer SQL Abfrage.

Verwenden Sie PerfAddCounters, um einem Abfragehandle Abfragen hinzuzufügen. Verwenden Sie PerfDeleteCounters, um Abfragen aus einem Abfragehandle zu entfernen.

Nachdem Sie die Abfragen in einem Abfragehandle geändert haben, verwenden Sie PerfQueryCounterInfo, um die Abfrageindizes abzurufen. Die Indizes geben die Reihenfolge an, in der die Abfrageergebnisse von PerfQueryCounterData zurückgegeben werden (die Ergebnisse stimmen nicht immer mit der Reihenfolge überein, in der die Abfragen hinzugefügt wurden).

Sobald das Abfragehandle bereit ist, verwenden Sie PerfQueryCounterData, um die Daten zu sammeln. Normalerweise sammeln Sie die Daten in regelmäßigen Abständen (einmal pro Sekunde oder einmal pro Minute) und verarbeiten die Daten nach Bedarf.

Hinweis

Leistungsindikatoren sind nicht so konzipiert, dass sie mehr als einmal pro Sekunde erfasst werden.

PerfQueryCounterData gibt einen PERF_DATA_HEADER -Block zurück, der aus einem Datenheader mit Zeitstempeln gefolgt von einer Sequenz von PERF_COUNTER_HEADER Blöcken besteht, die jeweils die Ergebnisse einer Abfrage enthalten.

PERF_COUNTER_HEADERkann verschiedene Arten von Daten enthalten, wie durch den Wert des dwType Felds angegeben:

  • PERF_ERROR_RETURN – PerfLib kann keine gültigen Indikatordaten vom Anbieter abrufen.
  • PERF_SINGLE_COUNTER – Die Abfrage wurde für einen einzelnen Indikator aus einem Einzelinstanz-Counterset verwendet. Die Ergebnisse enthalten nur den angeforderten Indikatorwert.
  • PERF_MULTIPLE_COUNTERS – Die Abfrage wurde für mehrere Leistungsindikatoren aus einem Eininstanz-Counterset verwendet. Das Ergebnis enthält die Indikatorwerte zusammen mit Informationen zum Abgleich jedes Werts mit dem entsprechenden Zähler (d. h. Spaltenüberschriften).
  • PERF_MULTIPLE_INSTANCES – Die Abfrage wurde für einen einzelnen Indikator aus einem Counterset mit mehreren Instanzen verwendet. Die Ergebnisse enthalten die Instanzinformationen (z. B. Zeilenüberschriften) und einen Indikatorwert pro Instanz.
  • PERF_COUNTERSET – Die Abfrage wurde für mehrere Leistungsindikatoren aus einem Multiinstanz-Counterset verwendet. Die Ergebnisse enthalten die Instanzinformationen (z. B. Zeilenüberschriften), die Indikatorwerte für jede Instanz und Informationen zum Abgleich jedes Werts mit dem entsprechenden Zähler (d. h. Spaltenüberschriften).

Die von PerfQueryCounterData zurückgegebenen Werte sind UINT32 - oder UINT64 -Rohwerte. Diese erfordern in der Regel eine Verarbeitung, um die erwarteten formatierten Werte zu erzeugen. Die erforderliche Verarbeitung hängt vom Typ des Indikators ab. Viele Indikatortypen erfordern zusätzliche Informationen für die vollständige Verarbeitung, z. B. einen Zeitstempel oder einen Wert aus einem "Basiszähler" im gleichen Beispiel.

Einige Indikatortypen sind "Delta"-Indikatoren, die nur im Vergleich mit den Daten aus einem vorherigen Beispiel sinnvoll sind. Ein Zähler vom Typ verfügt z. B. PERF_SAMPLE_COUNTER über einen formatierten Wert, der eine Rate anzeigen soll (die Häufigkeit, mit der ein bestimmtes Ding pro Sekunde im Stichprobenintervall passiert ist), aber der tatsächliche Rohwert ist nur eine Anzahl (die Häufigkeit, mit der ein bestimmtes Ding insgesamt passiert ist). Um den formatierten "rate"-Wert zu erzeugen, müssen Sie die Formel anwenden, die dem Indikatortyp entspricht. Die Formel für PERF_SAMPLE_COUNTER ist (N1 - N0) / ((T1 - T0) / F) : Subtrahieren Sie den Wert der aktuellen Stichprobe vom Wert der vorherigen Stichprobe (mit angabe der Häufigkeit, mit der das Ereignis während des Stichprobenintervalls aufgetreten ist), und dividieren Sie dann das Ergebnis durch die Anzahl der Sekunden im Samplingintervall (abgerufen durch Subtraktion des Zeitstempels der aktuellen Stichprobe vom Zeitstempel der vorherigen Stichprobe und Division durch Häufigkeit, um die Zeitspanne in Sekunden zu konvertieren).

Weitere Informationen zum Berechnen formatierter Werte aus Rohwerten finden Sie unter Berechnen von Indikatorwerten.

Beispiel

Der folgende Code implementiert einen Consumer, der die PerfLib V2-Consumerfunktionen verwendet, um CPU-Leistungsinformationen aus dem Leistungsindikatorsatz "Prozessorinformationen" zu lesen.

Der Consumer ist wie folgt organisiert:

  • Die CpuPerfCounters -Klasse implementiert die Logik zum Nutzen von Leistungsdaten. Sie kapselt ein Abfragehandle und einen Datenpuffer, in dem Abfrageergebnisse aufgezeichnet werden.
  • Die CpuPerfTimestamp Struktur speichert die Zeitstempelinformationen für ein Beispiel. Jedes Mal, wenn Daten gesammelt werden, empfängt der Aufrufer einen einzelnen CpuPerfTimestamp .
  • Die CpuPerfData Struktur speichert die Leistungsinformationen (Instanzname und rohe Leistungswerte) für eine CPU. Jedes Mal, wenn Daten gesammelt werden, empfängt der Aufrufer ein Array von CpuPerfData (eins pro CPU).

In diesem Beispiel werden hart codierte Counterset-GUID- und Indikator-ID-Werte verwendet, da es für ein bestimmtes Counterset (Prozessorinformationen) optimiert ist, das keine GUID- oder ID-Werte ändert. Eine allgemeinere Klasse, die Leistungsdaten aus beliebigen Indikatoren liest, muss PerfQueryCounterSetRegistrationInfo verwenden, um die Zuordnung zwischen Indikator-IDs und Indikatorwerten zur Laufzeit zu suchen.

Ein einfaches CpuPerfCountersConsumer.cpp Programm zeigt, wie die Werte aus der -Klasse verwendet CpuPerfCounters werden.

CpuPerfCounters.h

#pragma once
#include <sal.h>

// Contains timestamp data for a Processor Information data collection.
struct CpuPerfTimestamp
{
    __int64 PerfTimeStamp;   // Timestamp from the high-resolution clock.
    __int64 PerfTime100NSec; // The number of 100 nanosecond intervals since January 1, 1601, in Coordinated Universal Time (UTC).
    __int64 PerfFreq;        // The frequency of the high-resolution clock.
};

// Contains the raw data from a Processor Information sample.
// Note that the values returned are raw data. Converting from raw data to a
// friendly value may require computation, and the computation may require
// two samples of data. The computation depends on the Type, and the formula
// to use for each type can be found on MSDN.
// For example, ProcessorTime contains raw data of type PERF_100NSEC_TIMER_INV.
// Given two samples of data, s0 at time t0 and s1 at time t1, the friendly
// "% Processor Time" value is computed as:
// 100*(1-(s1.ProcessorTime-s0.ProcessorTime)/(t1.PerfTime100NSec-t0.PerfTime100NSec))
struct CpuPerfData
{
    wchar_t Name[16]; // Format: "NumaNode,NumaIndex", "NumaNode,_Total", or "_Total".
    __int64 unsigned ProcessorTime; // % Processor Time (#0, Type=PERF_100NSEC_TIMER_INV)
    __int64 unsigned UserTime; // % User Time (#1, Type=PERF_100NSEC_TIMER)
    __int64 unsigned PrivilegedTime; // % Privileged Time (#2, Type=PERF_100NSEC_TIMER)
    __int32 unsigned Interrupts; // Interrupts / sec (#3, Type=PERF_COUNTER_COUNTER)
    __int64 unsigned DpcTime; // % DPC Time (#4, Type=PERF_100NSEC_TIMER)
    __int64 unsigned InterruptTime; // % Interrupt Time (#5, Type=PERF_100NSEC_TIMER)
    __int32 unsigned DpcsQueued; // DPCs Queued / sec (#6, Type=PERF_COUNTER_COUNTER)
    __int32 unsigned Dpcs; // DPC Rate (#7, Type=PERF_COUNTER_RAWCOUNT)
    __int64 unsigned IdleTime; // % Idle Time (#8, Type=PERF_100NSEC_TIMER)
    __int64 unsigned C1Time; // % C1 Time (#9, Type=PERF_100NSEC_TIMER)
    __int64 unsigned C2Time; // % C2 Time (#10, Type=PERF_100NSEC_TIMER)
    __int64 unsigned C3Time; // % C3 Time (#11, Type=PERF_100NSEC_TIMER)
    __int64 unsigned C1Transitions; // C1 Transitions / sec (#12, Type=PERF_COUNTER_BULK_COUNT)
    __int64 unsigned C2Transitions; // C2 Transitions / sec (#13, Type=PERF_COUNTER_BULK_COUNT)
    __int64 unsigned C3Transitions; // C3 Transitions / sec (#14, Type=PERF_COUNTER_BULK_COUNT)
    __int64 unsigned PriorityTime; // % Priority Time (#15, Type=PERF_100NSEC_TIMER_INV)
    __int32 unsigned ParkingStatus; // Parking Status (#16, Type=PERF_COUNTER_RAWCOUNT)
    __int32 unsigned ProcessorFrequency; // Processor Frequency (#17, Type=PERF_COUNTER_RAWCOUNT)
    __int32 unsigned PercentMaximumFrequency; // % of Maximum Frequency (#18, Type=PERF_COUNTER_RAWCOUNT)
    __int32 unsigned ProcessorStateFlags; // Processor State Flags (#19, Type=PERF_COUNTER_RAWCOUNT)
    __int32 unsigned ClockInterrupts; // Clock Interrupts / sec (#20, Type=PERF_COUNTER_COUNTER)
    __int64 unsigned AverageIdleTime; // Average Idle Time (#21, Type=PERF_PRECISION_100NS_TIMER)
    __int64 unsigned AverageIdleTimeBase; // Average Idle Time Base (#22, Type=PERF_PRECISION_TIMESTAMP)
    __int64 unsigned IdleBreakEvents; // Idle Break Events / sec (#23, Type=PERF_COUNTER_BULK_COUNT)
    __int64 unsigned ProcessorPerformance; // % Processor Performance (#24, Type=PERF_AVERAGE_BULK)
    __int32 unsigned ProcessorPerformanceBase; // % Processor Performance Base (#25, Type=PERF_AVERAGE_BASE)
    __int64 unsigned ProcessorUtility; // % Processor Utility (#26, Type=PERF_AVERAGE_BULK)
    __int64 unsigned PrivilegedUtility; // % Privileged Utility (#28, Type=PERF_AVERAGE_BULK)
    __int32 unsigned UtilityBase; // % Utility Base (#27, Type=PERF_AVERAGE_BASE)
    __int32 unsigned PercentPerformanceLimit; // % Performance Limit (#30, Type=PERF_COUNTER_RAWCOUNT)
    __int32 unsigned PerformanceLimitFlags; // Performance Limit Flags (#31, Type=PERF_COUNTER_RAWCOUNT)
};

// Performs data collection from the Processor Information performance counter.
class CpuPerfCounters
{
public:

    CpuPerfCounters(CpuPerfCounters const&) = delete;
    void operator=(CpuPerfCounters const&) = delete;

    ~CpuPerfCounters();
    CpuPerfCounters() noexcept;

    // Reads CPU performance counter data.
    // Returns ERROR_SUCCESS (0) on success, ERROR_MORE_DATA if bufferCount is
    // too small, or another Win32 error code on failure.
    _Success_(return == 0)
    unsigned
    ReadData(
        _Out_opt_ CpuPerfTimestamp* timestamp,
        _Out_cap_post_count_(bufferCount, *bufferUsed) CpuPerfData* buffer,
        unsigned bufferCount,
        _Out_ unsigned* bufferUsed) noexcept;

private:

    void* m_hQuery;
    void* m_pData;
    unsigned m_cbData;
};

CpuPerfCounters.cpp

#include "CpuPerfCounters.h"
#include <windows.h>
#include <perflib.h>
#include <winperf.h>
#include <stdlib.h>

_Check_return_ _Ret_maybenull_ _Post_writable_byte_size_(cb)
static void*
AllocateBytes(unsigned cb) noexcept
{
    return malloc(cb);
}

static void
FreeBytes(_Pre_maybenull_ _Post_invalid_ void* p) noexcept
{
    if (p)
    {
        free(p);
    }
    return;
}

static void
AssignCounterValue(
    _Inout_ __int32 unsigned* pVar,
    _In_ PERF_COUNTER_DATA const* pData) noexcept
{
    if (pData->dwDataSize == 4 ||
        pData->dwDataSize == 8)
    {
        *pVar = *reinterpret_cast<__int32 unsigned const*>(pData + 1);
    }
}

static void
AssignCounterValue(
    _Inout_ __int64 unsigned* pVar,
    _In_ PERF_COUNTER_DATA const* pData) noexcept
{
    if (pData->dwDataSize == 8)
    {
        *pVar = *reinterpret_cast<__int64 unsigned const*>(pData + 1);
    }
    else if (pData->dwDataSize == 4)
    {
        *pVar = *reinterpret_cast<__int32 unsigned const*>(pData + 1);
    }
}

CpuPerfCounters::~CpuPerfCounters()
{
    if (m_hQuery)
    {
        PerfCloseQueryHandle(m_hQuery);
    }

    FreeBytes(m_pData);
}

CpuPerfCounters::CpuPerfCounters() noexcept
    : m_hQuery()
    , m_pData()
    , m_cbData()
{
    return;
}

_Success_(return == 0)
unsigned
CpuPerfCounters::ReadData(
    _Out_opt_ CpuPerfTimestamp* timestamp,
    _Out_cap_post_count_(bufferCount, *bufferUsed) CpuPerfData* buffer,
    unsigned bufferCount,
    _Out_ unsigned* bufferUsed) noexcept
{
    unsigned status;
    DWORD cbData;
    PERF_DATA_HEADER* pDataHeader;
    PERF_COUNTER_HEADER const* pCounterHeader;
    PERF_MULTI_COUNTERS const* pMultiCounters;
    PERF_MULTI_INSTANCES const* pMultiInstances;
    PERF_INSTANCE_HEADER const* pInstanceHeader;
    unsigned cInstances = 0;

    if (m_hQuery == nullptr)
    {
        HANDLE hQuery = 0;
        status = PerfOpenQueryHandle(nullptr, &hQuery);
        if (ERROR_SUCCESS != status)
        {
            goto Done;
        }

        struct
        {
            PERF_COUNTER_IDENTIFIER Identifier;
            WCHAR Name[4];
        } querySpec = {
            {
                // ProcessorCounterSetGuid
                { 0xb4fc721a, 0x378, 0x476f, 0x89, 0xba, 0xa5, 0xa7, 0x9f, 0x81, 0xb, 0x36 },
                0,
                sizeof(querySpec),
                PERF_WILDCARD_COUNTER, // Get all data for each CPU.
                0xFFFFFFFF // Get data for all instance IDs.
            },
            L"*" // Get data for all instance names.
        };

        status = PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec));
        if (ERROR_SUCCESS == status)
        {
            status = querySpec.Identifier.Status;
        }

        if (ERROR_SUCCESS != status)
        {
            PerfCloseQueryHandle(hQuery);
            goto Done;
        }

        // NOTE: A program that adds more than one query to the handle would need to call
        // PerfQueryCounterInfo to determine the order of the query results.

        m_hQuery = hQuery;
    }

    for (;;)
    {
        cbData = 0;
        pDataHeader = static_cast<PERF_DATA_HEADER*>(m_pData);
        status = PerfQueryCounterData(m_hQuery, pDataHeader, m_cbData, &cbData);
        if (ERROR_SUCCESS == status)
        {
            break;
        }
        else if (ERROR_NOT_ENOUGH_MEMORY != status)
        {
            goto Done;
        }

        FreeBytes(m_pData);
        m_cbData = 0;

        m_pData = AllocateBytes(cbData);
        if (nullptr == m_pData)
        {
            status = ERROR_OUTOFMEMORY;
            goto Done;
        }

        m_cbData = cbData;
    }

    // PERF_DATA_HEADER block = PERF_DATA_HEADER + dwNumCounters PERF_COUNTER_HEADER blocks
    if (cbData < sizeof(PERF_DATA_HEADER) ||
        cbData < pDataHeader->dwTotalSize ||
        pDataHeader->dwTotalSize < sizeof(PERF_DATA_HEADER) ||
        pDataHeader->dwNumCounters != 1)
    {
        status = ERROR_INVALID_DATA;
        goto Done;
    }

    // PERF_COUNTERSET block = PERF_COUNTER_HEADER + PERF_MULTI_COUNTERS block + PERF_MULTI_INSTANCES block
    cbData = pDataHeader->dwTotalSize - sizeof(PERF_DATA_HEADER);
    pCounterHeader = reinterpret_cast<PERF_COUNTER_HEADER*>(pDataHeader + 1);
    if (cbData < sizeof(PERF_COUNTER_HEADER) ||
        cbData < pCounterHeader->dwSize ||
        pCounterHeader->dwSize < sizeof(PERF_COUNTER_HEADER) ||
        PERF_COUNTERSET != pCounterHeader->dwType)
    {
        status = ERROR_INVALID_DATA;
        goto Done;
    }

    // PERF_MULTI_COUNTERS block = PERF_MULTI_COUNTERS + dwCounters DWORDs
    cbData = pCounterHeader->dwSize - sizeof(PERF_COUNTER_HEADER);
    pMultiCounters = reinterpret_cast<PERF_MULTI_COUNTERS const*>(pCounterHeader + 1);
    if (cbData < sizeof(PERF_MULTI_COUNTERS) ||
        cbData < pMultiCounters->dwSize ||
        pMultiCounters->dwSize < sizeof(PERF_MULTI_COUNTERS) ||
        (pMultiCounters->dwSize - sizeof(PERF_MULTI_COUNTERS)) / sizeof(DWORD) < pMultiCounters->dwCounters)
    {
        status = ERROR_INVALID_DATA;
        goto Done;
    }

    // PERF_MULTI_INSTANCES block = PERF_MULTI_INSTANCES + dwInstances instance data blocks
    cbData -= pMultiCounters->dwSize;
    pMultiInstances = reinterpret_cast<PERF_MULTI_INSTANCES const*>((LPCBYTE)pMultiCounters + pMultiCounters->dwSize);
    if (cbData < sizeof(PERF_MULTI_INSTANCES) ||
        cbData < pMultiInstances->dwTotalSize ||
        pMultiInstances->dwTotalSize < sizeof(PERF_MULTI_INSTANCES))
    {
        status = ERROR_INVALID_DATA;
        goto Done;
    }

    cInstances = pMultiInstances->dwInstances;
    if (bufferCount < cInstances)
    {
        status = ERROR_MORE_DATA;
        goto Done;
    }

    memset(buffer, 0, sizeof(buffer[0]) * cInstances);

    cbData = pMultiInstances->dwTotalSize - sizeof(PERF_MULTI_INSTANCES);
    pInstanceHeader = reinterpret_cast<PERF_INSTANCE_HEADER const*>(pMultiInstances + 1);
    for (unsigned iInstance = 0; iInstance != pMultiInstances->dwInstances; iInstance += 1)
    {
        CpuPerfData& d = buffer[iInstance];

        // instance data block = PERF_INSTANCE_HEADER block + dwCounters PERF_COUNTER_DATA blocks
        if (cbData < sizeof(PERF_INSTANCE_HEADER) ||
            cbData < pInstanceHeader->Size ||
            pInstanceHeader->Size < sizeof(PERF_INSTANCE_HEADER))
        {
            status = ERROR_INVALID_DATA;
            goto Done;
        }

        unsigned const instanceNameMax = (pInstanceHeader->Size - sizeof(PERF_INSTANCE_HEADER)) / sizeof(WCHAR);
        WCHAR const* const instanceName = reinterpret_cast<WCHAR const*>(pInstanceHeader + 1);
        if (instanceNameMax == wcsnlen(instanceName, instanceNameMax))
        {
            status = ERROR_INVALID_DATA;
            goto Done;
        }

        wcsncpy_s(d.Name, instanceName, _TRUNCATE);

        cbData -= pInstanceHeader->Size;
        PERF_COUNTER_DATA const* pCounterData = reinterpret_cast<PERF_COUNTER_DATA const*>((LPCBYTE)pInstanceHeader + pInstanceHeader->Size);
        for (unsigned iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter += 1)
        {
            if (cbData < sizeof(PERF_COUNTER_DATA) ||
                cbData < pCounterData->dwSize ||
                pCounterData->dwSize < sizeof(PERF_COUNTER_DATA) + 8 ||
                pCounterData->dwSize - sizeof(PERF_COUNTER_DATA) < pCounterData->dwDataSize)
            {
                status = ERROR_INVALID_DATA;
                goto Done;
            }

            DWORD const* pCounterIds = reinterpret_cast<DWORD const*>(pMultiCounters + 1);
            switch (pCounterIds[iCounter])
            {
            case 0: // PERF_100NSEC_TIMER_INV
                AssignCounterValue(&d.ProcessorTime, pCounterData);
                break;
            case 1: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.UserTime, pCounterData);
                break;
            case 2: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.PrivilegedTime, pCounterData);
                break;
            case 3: // PERF_COUNTER_COUNTER
                AssignCounterValue(&d.Interrupts, pCounterData);
                break;
            case 4: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.DpcTime, pCounterData);
                break;
            case 5: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.InterruptTime, pCounterData);
                break;
            case 6: // PERF_COUNTER_COUNTER
                AssignCounterValue(&d.DpcsQueued, pCounterData);
                break;
            case 7: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.Dpcs, pCounterData);
                break;
            case 8: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.IdleTime, pCounterData);
                break;
            case 9: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.C1Time, pCounterData);
                break;
            case 10: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.C2Time, pCounterData);
                break;
            case 11: // PERF_100NSEC_TIMER
                AssignCounterValue(&d.C3Time, pCounterData);
                break;
            case 12: // PERF_COUNTER_BULK_COUNT
                AssignCounterValue(&d.C1Transitions, pCounterData);
                break;
            case 13: // PERF_COUNTER_BULK_COUNT
                AssignCounterValue(&d.C2Transitions, pCounterData);
                break;
            case 14: // PERF_COUNTER_BULK_COUNT
                AssignCounterValue(&d.C3Transitions, pCounterData);
                break;
            case 15: // PERF_100NSEC_TIMER_INV
                AssignCounterValue(&d.PriorityTime, pCounterData);
                break;
            case 16: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.ParkingStatus, pCounterData);
                break;
            case 17: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.ProcessorFrequency, pCounterData);
                break;
            case 18: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.PercentMaximumFrequency, pCounterData);
                break;
            case 19: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.ProcessorStateFlags, pCounterData);
                break;
            case 20: // PERF_COUNTER_COUNTER
                AssignCounterValue(&d.ClockInterrupts, pCounterData);
                break;
            case 21: // PERF_PRECISION_100NS_TIMER
                AssignCounterValue(&d.AverageIdleTime, pCounterData);
                break;
            case 22: // PERF_PRECISION_TIMESTAMP
                AssignCounterValue(&d.AverageIdleTimeBase, pCounterData);
                break;
            case 23: // PERF_COUNTER_BULK_COUNT
                AssignCounterValue(&d.IdleBreakEvents, pCounterData);
                break;
            case 24: // PERF_AVERAGE_BULK
                AssignCounterValue(&d.ProcessorPerformance, pCounterData);
                break;
            case 25: // PERF_AVERAGE_BASE
                AssignCounterValue(&d.ProcessorPerformanceBase, pCounterData);
                break;
            case 26: // PERF_AVERAGE_BULK
                AssignCounterValue(&d.ProcessorUtility, pCounterData);
                break;
            case 28: // PERF_AVERAGE_BULK
                AssignCounterValue(&d.PrivilegedUtility, pCounterData);
                break;
            case 27: // PERF_AVERAGE_BASE
                AssignCounterValue(&d.UtilityBase, pCounterData);
                break;
            case 30: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.PercentPerformanceLimit, pCounterData);
                break;
            case 31: // PERF_COUNTER_RAWCOUNT
                AssignCounterValue(&d.PerformanceLimitFlags, pCounterData);
                break;
            }

            cbData -= pCounterData->dwSize;
            pCounterData = reinterpret_cast<PERF_COUNTER_DATA const*>((LPCBYTE)pCounterData + pCounterData->dwSize);
        }

        pInstanceHeader = reinterpret_cast<PERF_INSTANCE_HEADER const*>(pCounterData);
    }

    if (nullptr != timestamp)
    {
        timestamp->PerfTimeStamp = pDataHeader->PerfTimeStamp;
        timestamp->PerfTime100NSec = pDataHeader->PerfTime100NSec;
        timestamp->PerfFreq = pDataHeader->PerfFreq;
    }

    status = ERROR_SUCCESS;

Done:

    *bufferUsed = cInstances;
    return status;
}

CpuPerfCountersConsumer.cpp

#include <windows.h>
#include <stdio.h>
#include "CpuPerfCounters.h"
#include <utility>

int wmain()
{
    unsigned status;
    unsigned const dataMax = 30; // Support up to 30 instances
    CpuPerfCounters cpc;
    CpuPerfTimestamp timestamp[2];
    CpuPerfTimestamp* t0 = timestamp + 0;
    CpuPerfTimestamp* t1 = timestamp + 1;
    CpuPerfData data[dataMax * 2];
    CpuPerfData* d0 = data + 0;
    CpuPerfData* d1 = data + dataMax;
    unsigned used;

    status = cpc.ReadData(t0, d0, dataMax, &used);
    printf("ReadData0 used=%u, status=%u\n", used, status);

    for (unsigned iSample = 1; iSample != 10; iSample += 1)
    {
        Sleep(1000);
        status = cpc.ReadData(t1, d1, dataMax, &used);
        printf("ReadData%u used=%u, status=%u\n", iSample, used, status);

        if (status == ERROR_SUCCESS && used != 0)
        {
            // Show the ProcessorTime value from instance #0 (usually the "_Total" instance):
            auto& s0 = d0[0];
            auto& s1 = d1[0];
            printf("  %ls/%ls = %f\n", s0.Name, s1.Name,
                100.0 * (1.0 - static_cast<double>(s1.ProcessorTime - s0.ProcessorTime) / static_cast<double>(t1->PerfTime100NSec - t0->PerfTime100NSec)));

            std::swap(t0, t1); // Swap "current" and "previous" timestamp pointers.
            std::swap(d0, d1); // Swap "current" and "previous" sample pointers.
        }
    }

    return status;
}