Partilhar via


Usando as funções PerfLib para consumir dados de contador

Use as funções perfLib Consumer para consumir dados de desempenho de provedores de dados de desempenho V2 quando você não puder usar as funções PDH (Performance Data Helper). Essas funções podem ser usadas ao escrever OneCore aplicativos para coletar contadores V2 ou quando você precisa coletar contadores V2 com dependências mínimas e sobrecarga.

Dica

As funções de Consumidor PerfLib V2 são mais difíceis de usar do que as funções PDH (Performance Data Helper) e dão suporte apenas à coleta de dados de provedores V2. As funções PDH devem ser preferenciais para a maioria dos aplicativos.

As funções de Consumidor Do PerfLib V2 são a API de baixo nível para coletar dados de provedores V2. As funções de Consumidor Do PerfLib V2 não dão suporte à coleta de dados de provedores V1.

Aviso

As funções de consumidor do PerfLib V2 podem potencialmente coletar dados de fontes não confiáveis, por exemplo, de serviços locais com privilégio limitado ou de computadores remotos. As funções de consumidor PerfLib V2 não validam os dados quanto à integridade ou consistência. Cabe ao seu aplicativo consumidor verificar se os dados retornados são consistentes, por exemplo, que os valores de Tamanho no bloco de dados retornado não excedem o tamanho real do bloco de dados retornado. Isso é especialmente importante quando o aplicativo consumidor é executado com privilégios elevados.

Uso de PerfLib

O perflib.h cabeçalho inclui as declarações usadas pelos provedores de modo de usuário V2 (ou seja, a API do Provedor PerfLib) e os consumidores V2 (ou seja, a API de Consumidor PerfLib). Ele declara as seguintes funções para consumir dados de desempenho V2:

  • Use PerfEnumerateCounterSet para obter os GUIDs dos contadores registrados pelos provedores V2 no sistema.
  • Use PerfQueryCounterSetRegistrationInfo para obter informações sobre um contador específico, como o nome do contador, a descrição, o tipo (instância única ou várias instâncias), tipos de contador, nomes de contador e descrições do contador.
  • Use PerfEnumerateCounterSetInstances para obter os nomes das instâncias atualmente ativas de um contraconjunto de várias instâncias.
  • Use PerfOpenQueryHandle para criar um novo identificador de consulta a ser usado para coletar dados de um ou mais contadores.
  • Use PerfCloseQueryHandle para fechar um identificador de consulta.
  • Use PerfAddCounters para adicionar uma consulta a um identificador de consulta.
  • Use PerfDeleteCounters para remover uma consulta de um identificador de consulta.
  • Use PerfQueryCounterInfo para obter informações sobre as consultas atuais em um identificador de consulta.
  • Use PerfQueryCounterData para coletar os dados de desempenho de um identificador de consulta.

Se o consumidor estiver consumindo dados somente de um provedor específico em que os índices GUID e contador estão estáveis e você tiver acesso ao arquivo de símbolo gerado por CTRPP (do parâmetro CTRPP -ch ), você poderá codificar o GUID do contador e os valores de índice do contador em seu consumidor.

Caso contrário, você precisará carregar metadados de contador para determinar os GUID(s) de contador(s) e os índices de contador a serem usados em sua consulta da seguinte maneira:

  • Use PerfEnumerateCounterSet para obter uma lista de GUIDs de contraconjunto com suporte.
  • Para cada GUID, use PerfQueryCounterSetRegistrationInfo para carregar o nome do contador. Pare quando encontrar o nome que está procurando.
  • Use PerfQueryCounterSetRegistrationInfo para carregar os metadados restantes (configuração de contador, nomes de contador, índices de contador, tipos de contador) para esse contador.

Se você só precisar saber os nomes das instâncias atualmente ativas de um contador (ou seja, se não precisar dos valores reais de dados de desempenho), poderá usar PerfEnumerateCounterSetInstances. Isso usa um GUID de contador como entrada e retorna um PERF_INSTANCE_HEADER bloco com os nomes e as IDs das instâncias atualmente ativas do contraconjunto fornecido.

Consultas

Para coletar dados de desempenho, você precisa criar um identificador de consulta, adicionar consultas a ele e coletar os dados das consultas.

Um identificador de consulta pode ter muitas consultas associadas a ele. Quando você chama PerfQueryCounterData para um identificador de consulta, PerfLib executará todas as consultas do identificador e coletará todos os resultados.

Cada consulta especifica um GUID de contador, um filtro de nome de instância, um filtro de ID de instância opcional e um filtro de ID de contador opcional.

  • O GUID do contador é necessário. Indica o GUID do conjunto de contadores do qual a consulta coletará dados. Isso é semelhante à FROM cláusula de uma consulta SQL.
  • O filtro de nome da instância é necessário. Indica um padrão curinga que o nome da instância deve corresponder para que a instância seja incluída na consulta, indicando * "qualquer caractere" e ? indicando "um caractere". Para contadores de instância única, isso DEVE ser definido como uma cadeia de caracteres de comprimento ""zero . Para contadores de várias instâncias, isso DEVE ser definido como uma cadeia de caracteres não vazia (use "*" para aceitar todos os nomes de instância). Isso é semelhante a uma WHERE InstanceName LIKE NameFilter cláusula de uma consulta SQL.
  • O filtro de ID da instância é opcional. Se presente (ou seja, se definido como um valor diferente 0xFFFFFFFFde ), isso indica que a consulta só deve coletar instâncias em que a ID da instância corresponde à ID especificada. Se ausente (ou seja, se definido 0xFFFFFFFFcomo ), isso indica que a consulta deve aceitar todas as IDs de instância. Isso é semelhante a uma WHERE InstanceId == IdFilter cláusula de uma consulta SQL.
  • O filtro de ID do contador é opcional. Se presente (ou seja, se definido como um valor diferente PERF_WILDCARD_COUNTERde ), isso indica que a consulta deve coletar um único contador, semelhante a uma SELECT CounterName cláusula de uma consulta SQL. Se ausente (ou seja, se definido PERF_WILDCARD_COUNTERcomo ), isso indica que a consulta deve coletar todos os contadores disponíveis, semelhante a uma SELECT * cláusula de uma consulta SQL.

Use PerfAddCounters para adicionar consultas a um identificador de consulta. Use PerfDeleteCounters para remover consultas de um identificador de consulta.

Depois de alterar as consultas em um identificador de consulta, use PerfQueryCounterInfo para obter os índices de consulta. Os índices indicam a ordem na qual os resultados da consulta serão retornados por PerfQueryCounterData (os resultados nem sempre corresponderão à ordem em que as consultas foram adicionadas).

Quando o identificador de consulta estiver pronto, use PerfQueryCounterData para coletar os dados. Normalmente, você coletará os dados periodicamente (uma vez por segundo ou uma vez por minuto) e processará os dados conforme necessário.

Observação

Os contadores de desempenho não foram projetados para serem coletados mais de uma vez por segundo.

PerfQueryCounterData retornará um PERF_DATA_HEADER bloco, que consiste em um cabeçalho de dados com carimbos de data/hora seguidos por uma sequência de PERF_COUNTER_HEADER blocos, cada um contendo os resultados de uma consulta.

O PERF_COUNTER_HEADER pode conter vários tipos diferentes de dados, conforme indicado pelo valor do dwType campo:

  • PERF_ERROR_RETURN – O PerfLib não pode obter dados de contador válidos de volta do provedor.
  • PERF_SINGLE_COUNTER - A consulta era para um único contador de um contador de instância única. Os resultados contêm apenas o valor do contador solicitado.
  • PERF_MULTIPLE_COUNTERS - A consulta era para vários contadores de um contador de instância única. O resultado contém os valores do contador, juntamente com informações para corresponder cada valor com o contador correspondente (ou seja, títulos de coluna).
  • PERF_MULTIPLE_INSTANCES - A consulta era para um único contador de um contador de várias instâncias. Os resultados contêm as informações da instância (ou seja, títulos de linha) e um valor de contador por instância.
  • PERF_COUNTERSET - A consulta era para vários contadores de um contraconjunto de várias instâncias. Os resultados contêm as informações da instância (ou seja, títulos de linha), os valores de contador para cada instância e informações para corresponder cada valor com o contador correspondente (ou seja, títulos de coluna).

Os valores retornados por PerfQueryCounterData são UINT32 valores brutos ou UINT64 . Geralmente, eles exigem algum processamento para produzir os valores formatados esperados. O processamento necessário depende do tipo do contador. Muitos tipos de contador exigem informações adicionais para processamento completo, como um carimbo de data/hora ou um valor de um contador "base" no mesmo exemplo.

Alguns tipos de contador são contadores "delta" que só são significativos quando comparados com os dados de um exemplo anterior. Por exemplo, um contador do tipo PERF_SAMPLE_COUNTER tem um valor formatado que deve mostrar uma taxa (o número de vezes que uma coisa específica ocorreu por segundo durante o intervalo de amostragem), mas o valor bruto real é apenas uma contagem (o número de vezes que uma coisa específica aconteceu no total). Para produzir o valor de "taxa" formatado, você deve aplicar a fórmula correspondente ao tipo de contador. A fórmula para PERF_SAMPLE_COUNTER é (N1 - N0) / ((T1 - T0) / F): subtraia o valor da amostra atual do valor da amostra anterior (fornecendo o número de vezes que o evento ocorreu durante o intervalo de amostra) e divida o resultado pelo número de segundos no intervalo de amostra (obtido subtraindo o carimbo de data/hora da amostra atual do carimbo de data/hora da amostra anterior e dividindo por frequência para converter o intervalo de tempo em segundos).

Consulte Calculando valores de contador para obter mais informações sobre como calcular valores formatados de valores brutos.

Amostra

O código a seguir implementa um consumidor que usa as funções de Consumidor PerfLib V2 para ler informações de desempenho da CPU do contador "Informações do Processador".

O consumidor é organizado da seguinte maneira:

  • A CpuPerfCounters classe implementa a lógica para consumir dados de desempenho. Ele encapsula um identificador de consulta e um buffer de dados no qual os resultados da consulta são registrados.
  • O CpuPerfTimestamp struct armazena as informações de carimbo de data/hora de um exemplo. Cada vez que os dados são coletados, o chamador recebe um único CpuPerfTimestamp.
  • O CpuPerfData struct armazena as informações de desempenho (nome da instância e valores brutos de desempenho) para uma CPU. Cada vez que os dados são coletados, o chamador recebe uma matriz de CpuPerfData (uma por CPU).

Este exemplo usa guid de contador embutido em código e valores de ID do contador porque é otimizado para um contador específico (Informações do Processador) que não alterará os valores de GUID ou ID. Uma classe mais genérica que lê dados de desempenho de contadores arbitrários precisaria usar PerfQueryCounterSetRegistrationInfo para pesquisar o mapeamento entre IDs de contador e valores de contador no runtime.

Um programa simples CpuPerfCountersConsumer.cpp mostra como usar os valores da 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.
// 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;
}