Verwenden der PerfLib-Funktionen zum Verarbeiten von Leistungsindikatordaten

Verwenden Sie die PerfLib Consumer-Funktionen, um Leistungsdaten von V2-Leistungsdatenanbietern zu nutzen, wenn Sie die PDH-Funktionen (Performance Data Helper) nicht verwenden können. Diese Funktionen können beim Schreiben OneCore Anwendungen verwendet werden, um V2-Countersets zu sammeln, oder wenn Sie V2-Indikatorensätze mit minimalen Abhängigkeiten und aufwand sammeln müssen.

Tipp

Die PerfLib V2 Consumer-Funktionen 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 Consumer-Funktionen sind die LOW-Level-API zum Sammeln von Daten von V2-Anbietern. Die PerfLib V2 Consumer-Funktionen unterstützen das Sammeln von Daten von V1-Anbietern nicht.

Warnung

Die PerfLib V2-Consumerfunktionen können potenziell 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. ob die Größenwerte 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. Es deklariert die folgenden Funktionen für die Nutzung von V2-Leistungsdaten:

  • Verwenden Sie PerfEnumerateCounterSet , um die GUIDs der Indikatorensätze abzurufen, die von den V2-Anbietern im System registriert werden.
  • Verwenden Sie PerfQueryCounterSetRegistrationInfo, um Informationen zu einem bestimmten Leistungssatz abzurufen, z. B. Name, Beschreibung, Typ (single-instance oder multi-instance), Indikatortypen, Indikatornamen und Leistungsindikatorbeschreibungen.
  • Verwenden Sie PerfEnumerateCounterCounterSetInstances, um die Namen der derzeit aktiven Instanzen eines Multi-instance-Countersets 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 Daten nur von einem bestimmten Anbieter verwendet, bei dem die GUID- und Zählerindizes stabil sind und Sie Zugriff auf die von CTRPP generierte Symboldatei (aus dem CTRPP-Parameter -ch ) haben, können Sie die Werte der Gegensatz-GUID und des Indikatorindexes hart in Ihren Consumer codieren.

Andernfalls müssen Sie Countersetmetadaten laden, um die Zählerset-GUID(s) und die Indikatorenindizes zu bestimmen, die in Ihrer Abfrage wie folgt verwendet werden sollen:

  • Verwenden Sie PerfEnumerateCounterSet , um eine Liste der unterstützten Counterset-GUIDs abzurufen.
  • Verwenden Sie für jede GUID PerfQueryCounterSetRegistrationInfo , um den Namen der Indikatorengruppe zu laden. Beenden Sie, wenn Sie den gesuchten Namen gefunden haben.
  • Verwenden Sie PerfQueryCounterSetRegistrationInfo , um die verbleibenden Metadaten (Countersetkonfiguration, Indikatornamen, Indikatorindizes, Indikatortypen) für dieses Leistungsset 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 PerfEnumerateCounterAteSetInstances verwenden. Dies verwendet eine Gegensatz-GUID als Eingabe und gibt einen PERF_INSTANCE_HEADER Block mit den Namen und IDs der derzeit aktiven Instanzen der angegebenen Indikatorenmenge zurück.

Abfragen

Um Leistungsdaten zu sammeln, 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 sammelt alle Ergebnisse.

Jede Abfrage gibt eine Gegensatz-GUID, einen instance Namensfilter, einen optionalen instance-ID-Filter und einen optionalen Indikator-ID-Filter an.

  • Die Gegensatz-GUID ist erforderlich. Sie gibt die GUID des Leistungsindikators an, aus dem die Abfrage Daten sammelt. Dies ähnelt der FROM -Klausel einer SQL-Abfrage.
  • Der instance Namensfilter ist erforderlich. Es gibt ein Wildcardmuster an, mit dem der instance Name übereinstimmen muss, damit der instance in die Abfrage aufgenommen werden kann, wobei * "beliebige Zeichen" und ? "ein Zeichen" angegeben werden. Für einzelne instance-Countersets MUSS dies auf eine Zeichenfolge ""der Länge null festgelegt werden. Bei Multi-instance-Indikatorensätzen MUSS dies auf eine nicht leere Zeichenfolge festgelegt werden (verwenden Sie"*", um alle instance Namen zu akzeptieren). Dies ähnelt einer WHERE InstanceName LIKE NameFilter -Klausel einer SQL-Abfrage.
  • Der filter instance ID ist optional. Wenn vorhanden (d. h. wenn sie auf einen anderen Wert als 0xFFFFFFFFfestgelegt ist), gibt dies an, dass die Abfrage nur Instanzen sammeln soll, bei denen die instance ID mit der angegebenen ID übereinstimmt. Wenn dieser Wert nicht vorhanden ist (d. h. wenn er auf 0xFFFFFFFFfestgelegt ist), bedeutet dies, dass die Abfrage alle instance IDs akzeptieren soll. Dies ähnelt einer WHERE InstanceId == IdFilter -Klausel einer SQL-Abfrage.
  • Der Zähler-ID-Filter ist optional. Wenn vorhanden (d. h. auf einen anderen Wert als PERF_WILDCARD_COUNTERfestgelegt) gibt dies an, dass die Abfrage einen einzelnen Leistungsindikator erfassen soll, ähnlich wie bei einer SELECT CounterName Klausel einer SQL-Abfrage. Wenn sie nicht vorhanden ist (d. h. wenn sie auf PERF_WILDCARD_COUNTERfestgelegt ist), gibt dies an, dass die Abfrage alle verfügbaren Leistungsindikatoren erfassen soll, ähnlich wie bei einer SELECT * Klausel einer SQL-Abfrage.

Verwenden Sie PerfAddCounters , um Abfragen zu einem Abfragehandle 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 dann 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.

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

  • PERF_ERROR_RETURN – PerfLib kann keine gültigen Zählerdaten vom Anbieter zurückholen.
  • PERF_SINGLE_COUNTER– Die Abfrage war für einen einzelnen Zähler aus einer einzelnen instance Counterset. Die Ergebnisse enthalten nur den angeforderten Zählerwert.
  • PERF_MULTIPLE_COUNTERS– Die Abfrage war für mehrere Leistungsindikatoren aus einem einzelnen instance Counterset vorgesehen. Das Ergebnis enthält die Zählerwerte zusammen mit Informationen zum Abgleich jedes Werts mit dem entsprechenden Zähler (z. B. Spaltenüberschriften).
  • PERF_MULTIPLE_INSTANCES– Die Abfrage war für einen einzelnen Zähler aus einem Multi-instance-Counterset. Die Ergebnisse enthalten die instance Informationen (z. B. Zeilenüberschriften) und einen Zählerwert pro instance.
  • PERF_COUNTERSET– Die Abfrage galt für mehrere Leistungsindikatoren aus einem Multi-instance-Counterset. Die Ergebnisse enthalten die instance Informationen (d. h. Zeilenüberschriften), die Zählerwerte für die einzelnen instance und Informationen zum Abgleich jedes Werts mit dem entsprechenden Zähler (d. h. Spaltenüberschriften).

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

Einige Leistungsindikatortypen sind "Delta"-Indikatoren, die nur im Vergleich mit den Daten aus einem vorherigen Beispiel aussagekräftig sind. Beispielsweise verfügt ein Zähler vom Typ PERF_SAMPLE_COUNTER über einen formatierten Wert, der voraussichtlich eine Rate anzeigt (die Häufigkeit, mit der ein bestimmter Vorgang pro Sekunde im Stichprobenintervall passiert ist), aber der tatsächliche Rohwert ist nur eine Anzahl (die Häufigkeit, wie oft eine bestimmte Sache insgesamt passiert ist). Um den formatierten "rate"-Wert zu erzeugen, müssen Sie die Formel anwenden, die dem Zählertyp entspricht. Die Formel für PERF_SAMPLE_COUNTER lautet (N1 - N0) / ((T1 - T0) / F): Subtrahieren Sie den Wert der aktuellen Stichprobe vom Wert der vorherigen Stichprobe (gibt an, wie oft das Ereignis während des Stichprobenintervalls aufgetreten ist), und dividieren Sie das Ergebnis dann durch die Anzahl der Sekunden im Stichprobenintervall (erhalten, indem Sie den Zeitstempel der aktuellen Stichprobe vom Zeitstempel der vorherigen Stichprobe subtrahieren und durch Häufigkeit dividieren, um den Zeitraum in Sekunden zu konvertieren).

Weitere Informationen zum Berechnen formatierter Werte aus Rohwerten finden Sie unter Berechnen von Zählerwerten .

Beispiel

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

Der Consumer ist wie folgt organisiert:

  • Die CpuPerfCounters -Klasse implementiert die Logik für die Nutzung von Leistungsdaten. Es 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, erhält der Aufrufer eine einzelne CpuPerfTimestamp.
  • Die CpuPerfData Struktur speichert die Leistungsinformationen (instance Namen und Rohleistungswerte) für eine CPU. Jedes Mal, wenn Daten gesammelt werden, erhält der Aufrufer ein Array von CpuPerfData (eins pro CPU).

In diesem Beispiel werden hartcodierte Indikatorenset-GUID- und Indikator-ID-Werte verwendet, da es für ein bestimmtes Zählerset (Prozessorinformationen) optimiert ist, das keine GUID- oder ID-Werte ändert. Eine allgemeinere Klasse, die Leistungsdaten aus beliebigen Leistungsindikatoren liest, müsste PerfQueryCounterSetRegistrationInfo verwenden, um die Zuordnung zwischen Indikator-IDs und Indikatorwerten zur Laufzeit nachzuschlagen.

Ein einfaches CpuPerfCountersConsumer.cpp Programm zeigt, wie die Werte aus der CpuPerfCounters -Klasse verwendet 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;
}