Utilisation des fonctions PerfLib pour consommer des données de compteurs

Utilisez les fonctions PerfLib Consumer pour consommer des données de performances à partir de fournisseurs de données de performance V2 lorsque vous ne pouvez pas utiliser les fonctions d’assistance des données de performance (PDH). Ces fonctions peuvent être utilisées lors de l’écriture de OneCore applications pour collecter des compteurs V2 ou lorsque vous devez collecter des compteurs V2 avec des dépendances et une surcharge minimales.

Conseil

Les fonctions PerfLib V2 Consumer sont plus difficiles à utiliser que les fonctions d’assistance des données de performance (PDH) et prennent uniquement en charge la collecte de données à partir de fournisseurs V2. Les fonctions PDH doivent être préférées pour la plupart des applications.

Les fonctions De consommation PerfLib V2 sont l’API de bas niveau permettant de collecter des données à partir de fournisseurs V2. Les fonctions De consommation PerfLib V2 ne prennent pas en charge la collecte de données à partir de fournisseurs V1.

Avertissement

Les fonctions grand public PerfLib V2 peuvent potentiellement collecter des données à partir de sources non approuvées, par exemple à partir de services locaux à privilèges limités ou de machines distantes. Les fonctions de consommation PerfLib V2 ne valident pas les données à des fins d’intégrité ou de cohérence. Il incombe à votre application consommateur de vérifier que les données retournées sont cohérentes, par exemple, que les valeurs size du bloc de données retourné ne dépassent pas la taille réelle du bloc de données retourné. Cela est particulièrement important lorsque l’application consommateur s’exécute avec des privilèges élevés.

Utilisation de PerfLib

L’en-tête perflib.h inclut les déclarations utilisées par les fournisseurs de mode utilisateur V2 (c’est-à-dire l’API fournisseur PerfLib) et les consommateurs V2 (c’est-à-dire l’API consommateur PerfLib). Il déclare les fonctions suivantes pour l’utilisation des données de performances V2 :

  • Utilisez PerfEnumerateCounterSet pour obtenir les GUID des compteurs inscrits par les fournisseurs V2 sur le système.
  • Utilisez PerfQueryCounterSetRegistrationInfo pour obtenir des informations sur un compteur particulier, comme le nom, la description, le type (mono-instance ou plusieurs instance), les types de compteurs, les noms de compteurs et les descriptions des compteurs.
  • Utilisez PerfEnumerateCounterSetInstances pour obtenir les noms des instances actuellement actives d’un compteur multi-instance.
  • Utilisez PerfOpenQueryHandle pour créer un nouveau handle de requête à utiliser pour collecter des données à partir d’un ou plusieurs compteurs.
  • Utilisez PerfCloseQueryHandle pour fermer un handle de requête.
  • Utilisez PerfAddCounters pour ajouter une requête à un handle de requête.
  • Utilisez PerfDeleteCounters pour supprimer une requête d’un handle de requête.
  • Utilisez PerfQueryCounterInfo pour obtenir des informations sur les requêtes actuelles sur un handle de requête.
  • Utilisez PerfQueryCounterData pour collecter les données de performances à partir d’un handle de requête.

Si votre consommateur consomme des données uniquement à partir d’un fournisseur spécifique où le GUID et les index de compteur sont stables et que vous avez accès au fichier de symboles généré par CTRPP (à partir du paramètre CTRPP -ch ), vous pouvez coder en dur les valeurs d’index de compteur et de compteur dans votre consommateur.

Dans le cas contraire, vous devrez charger les métadonnées du compteur pour déterminer le ou les GUID de compteur et les index de compteur à utiliser dans votre requête comme suit :

  • Utilisez PerfEnumerateCounterSet pour obtenir la liste des GUID de jeu de compteurs pris en charge.
  • Pour chaque GUID, utilisez PerfQueryCounterSetRegistrationInfo pour charger le nom du compteur. Arrêtez lorsque vous trouvez le nom que vous recherchez.
  • Utilisez PerfQueryCounterSetRegistrationInfo pour charger les métadonnées restantes (configuration du compteur, noms des compteurs, index de compteur, types de compteurs) pour ce compteur.

Si vous avez simplement besoin de connaître les noms des instances actuellement actives d’un compteur (c’est-à-dire si vous n’avez pas besoin des valeurs de données de performances réelles), vous pouvez utiliser PerfEnumerateCounterSetInstances. Cela prend un GUID de contre-ensemble en tant qu’entrée et retourne un PERF_INSTANCE_HEADER bloc avec les noms et LES ID des instances actuellement actives du compteur donné.

Requêtes

Pour collecter des données de performances, vous devez créer un handle de requête, y ajouter des requêtes et collecter les données à partir des requêtes.

Un handle de requête peut avoir de nombreuses requêtes associées. Lorsque vous appelez PerfQueryCounterData pour un handle de requête, PerfLib exécute toutes les requêtes du handle et collecte tous les résultats.

Chaque requête spécifie un GUID de compteurs, un filtre de nom instance, un filtre d’ID de instance facultatif et un filtre d’ID de compteur facultatif.

  • Le GUID de l’ensemble de compteurs est requis. Il indique le GUID du compteur à partir duquel la requête collecte des données. Ceci est similaire à la FROM clause d’une requête SQL.
  • Le filtre de nom instance est obligatoire. Il indique un modèle générique que le nom de l’instance doit correspondre pour que le instance soit inclus dans la requête, en * indiquant « tous les caractères » et ? en indiquant « un caractère ». Pour les contre-ensembles de instance, il DOIT être défini sur une chaîne ""de longueur nulle . Pour les compteurs multi-instance, il DOIT être défini sur une chaîne non vide (utilisez "*" pour accepter tous les noms instance). Ceci est similaire à une WHERE InstanceName LIKE NameFilter clause d’une requête SQL.
  • Le filtre ID instance est facultatif. S’il est présent (c’est-à-dire s’il est défini sur une valeur autre que 0xFFFFFFFF), cela indique que la requête ne doit collecter que les instances où l’ID de instance correspond à l’ID spécifié. En cas d’absence (c’est-à-dire si la valeur est définie sur 0xFFFFFFFF), cela indique que la requête doit accepter tous les ID instance. Ceci est similaire à une WHERE InstanceId == IdFilter clause d’une requête SQL.
  • Le filtre d’ID de compteur est facultatif. Si cette valeur est présente (c’est-à-dire si elle est définie sur une valeur autre que PERF_WILDCARD_COUNTER), cela indique que la requête doit collecter un compteur unique, semblable à une SELECT CounterName clause d’une requête SQL. En cas d’absence (c’est-à-dire si la valeur est définie sur PERF_WILDCARD_COUNTER), cela indique que la requête doit collecter tous les compteurs disponibles, comme une SELECT * clause d’une requête SQL.

Utilisez PerfAddCounters pour ajouter des requêtes à un handle de requête. Utilisez PerfDeleteCounters pour supprimer des requêtes d’un handle de requête.

Après avoir modifié les requêtes dans un handle de requête, utilisez PerfQueryCounterInfo pour obtenir les index de requête. Les index indiquent l’ordre dans lequel les résultats de la requête seront retournés par PerfQueryCounterData (les résultats ne correspondent pas toujours à l’ordre dans lequel les requêtes ont été ajoutées).

Une fois le handle de requête prêt, utilisez PerfQueryCounterData pour collecter les données. Vous collectez normalement les données régulièrement (une fois par seconde ou une minute), puis traitez les données en fonction des besoins.

Notes

Les compteurs de performances ne sont pas conçus pour être collectés plusieurs fois par seconde.

PerfQueryCounterData retourne un PERF_DATA_HEADER bloc, qui se compose d’un en-tête de données avec des horodatages suivis d’une séquence de PERF_COUNTER_HEADER blocs, chacun contenant les résultats d’une requête.

peut PERF_COUNTER_HEADER contenir différents types de données, comme indiqué par la valeur du dwType champ :

  • PERF_ERROR_RETURN - PerfLib ne peut pas récupérer les données de compteur valides du fournisseur.
  • PERF_SINGLE_COUNTER- La requête portait sur un seul compteur à partir d’un compteur à instance unique. Les résultats contiennent uniquement la valeur de compteur demandée.
  • PERF_MULTIPLE_COUNTERS- La requête visait plusieurs compteurs à partir d’un seul instance compteur. Le résultat contient les valeurs de compteur ainsi que les informations permettant de faire correspondre chaque valeur au compteur correspondant (c’est-à-dire les en-têtes de colonne).
  • PERF_MULTIPLE_INSTANCES- La requête portait sur un compteur unique à partir d’un compteur multi-instance. Les résultats contiennent les informations instance (par exemple, les titres de ligne) et une valeur de compteur par instance.
  • PERF_COUNTERSET- La requête concerne plusieurs compteurs d’un compteur à plusieurs instance. Les résultats contiennent les informations instance (c’est-à-dire les en-têtes de ligne), les valeurs de compteur pour chaque instance et les informations permettant de faire correspondre chaque valeur au compteur correspondant (par exemple, les en-têtes de colonne).

Les valeurs retournées par PerfQueryCounterData sont ou UINT64 des UINT32 valeurs brutes. Celles-ci nécessitent généralement un traitement pour produire les valeurs mises en forme attendues. Le traitement requis dépend du type du compteur. De nombreux types de compteurs nécessitent des informations supplémentaires pour un traitement complet, comme un horodatage ou une valeur d’un compteur de « base » dans le même exemple.

Certains types de compteurs sont des compteurs « delta » qui ne sont significatifs que par rapport aux données d’un exemple précédent. Par exemple, un compteur de type PERF_SAMPLE_COUNTER a une valeur mise en forme qui est censée afficher un taux (le nombre de fois où une chose particulière s’est produite par seconde au cours de l’intervalle de l’échantillon), mais la valeur brute réelle n’est qu’un nombre (le nombre de fois où une chose particulière s’est produite au total). Pour produire la valeur « rate » mise en forme, vous devez appliquer la formule correspondant au type de compteur. La formule pour PERF_SAMPLE_COUNTER est : soustraire la valeur de l’exemple actuel de la valeur de l’exemple précédent (en fonction du nombre de fois où l’événement s’est (N1 - N0) / ((T1 - T0) / F)produit pendant l’intervalle d’échantillon), puis diviser le résultat par le nombre de secondes dans l’intervalle d’échantillonnage (obtenu en soustrayant l’horodatage de l’échantillon actuel de l’horodatage de l’échantillon précédent et en divisant par fréquence pour convertir l’intervalle de temps en secondes).

Pour plus d’informations sur le calcul des valeurs mises en forme à partir de valeurs brutes, consultez Calcul des valeurs de compteur .

Exemple

Le code suivant implémente un consommateur qui utilise les fonctions Consommateur PerfLib V2 pour lire les informations sur les performances du processeur à partir du compteur « Informations sur le processeur ».

Le consommateur est organisé comme suit :

  • La CpuPerfCounters classe implémente la logique de consommation des données de performances. Il encapsule un handle de requête et une mémoire tampon de données dans laquelle les résultats de la requête sont enregistrés.
  • Le CpuPerfTimestamp struct stocke les informations d’horodatage d’un exemple. Chaque fois que des données sont collectées, l’appelant reçoit un seul CpuPerfTimestamp.
  • Le CpuPerfData struct stocke les informations de performances (instance nom et valeurs de performances brutes) pour un processeur. Chaque fois que des données sont collectées, l’appelant reçoit un tableau de CpuPerfData (un par processeur).

Cet exemple utilise des valeurs GUID et ID de compteur codés en dur, car il est optimisé pour un compteur spécifique (Informations processeur) qui ne modifie pas les valeurs GUID ou ID. Une classe plus générique qui lit les données de performances à partir de compteurs arbitraires doit utiliser PerfQueryCounterSetRegistrationInfo pour rechercher le mappage entre les ID de compteur et les valeurs de compteur au moment de l’exécution.

Un programme simple CpuPerfCountersConsumer.cpp montre comment utiliser les valeurs de la CpuPerfCounters classe .

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;
}