PerfLib 함수를 사용하여 카운터 데이터 사용

PDH(성능 데이터 도우미) 함수를 사용할 수 없는 경우 PerfLib 소비자 함수를 사용하여 V2 성능 데이터 공급자의 성능 데이터를 사용합니다. 이러한 함수는 V2 카운터 세트를 수집하기 위해 OneCore 애플리케이션을 작성하거나 최소한의 종속성과 오버헤드로 V2 카운터 세트를 수집해야 하는 경우에 사용될 수 있습니다.

PerfLib V2 소비자 함수는 PDH(성능 데이터 도우미) 함수보다 사용하기 어렵고 V2 공급자의 데이터 수집만 지원합니다. PDH 함수는 대부분의 애플리케이션에서 선호되어야 합니다.

PerfLib V2 소비자 함수는 V2 공급자로부터 데이터를 수집하기 위한 하위 수준 API입니다. PerfLib V2 소비자 함수는 V1 공급자의 데이터 수집을 지원하지 않습니다.

경고

PerfLib V2 소비자 함수는 신뢰할 수 없는 원본(예: 제한된 권한 로컬 서비스 또는 원격 머신에서)에서 데이터를 수집할 수 있습니다. PerfLib V2 소비자 함수는 무결성 또는 일관성에 대한 데이터의 유효성을 검사하지 않습니다. 반환된 데이터 블록의 크기 값이 반환된 데이터 블록의 실제 크기를 초과하지 않는 경우와 같이 반환된 데이터가 일치하는지 확인하는 것은 소비자 애플리케이션의 입니다. 이는 소비자 애플리케이션이 상승된 권한으로 실행되는 경우에 특히 중요합니다.

PerfLib 사용량

헤더에는 perflib.h V2 사용자 모드 공급자(예: PerfLib 공급자 API) 및 V2 소비자(즉, PerfLib 소비자 API)에서 사용하는 선언이 포함됩니다. V2 성능 데이터를 사용하려면 다음 함수를 선언합니다.

  • PerfEnumerateCounterSet를 사용하여 시스템의 V2 공급자가 등록한 카운터 세트의 GUID를 가져옵니다.
  • PerfQueryCounterSetRegistrationInfo를 사용하여 카운터 세트의 이름, 설명, 형식(단일 instance 또는 다중 instance), 카운터 형식, 카운터 이름 및 카운터 설명과 같은 특정 카운터 세트에 대한 정보를 가져옵니다.
  • PerfEnumerateCounterSetInstances를 사용하여 다중 instance 카운터 세트의 현재 활성 인스턴스 이름을 가져옵니다.
  • PerfOpenQueryHandle을 사용하여 하나 이상의 카운터 집합에서 데이터를 수집하는 데 사용할 새 쿼리 핸들을 만듭니다.
  • PerfCloseQueryHandle을 사용하여 쿼리 핸들을 닫습니다.
  • PerfAddCounters를 사용하여 쿼리 핸들에 쿼리를 추가합니다.
  • PerfDeleteCounters를 사용하여 쿼리 핸들에서 쿼리를 제거합니다.
  • PerfQueryCounterInfo를 사용하여 쿼리 핸들의 현재 쿼리에 대한 정보를 가져옵니다.
  • PerfQueryCounterData를 사용하여 쿼리 핸들에서 성능 데이터를 수집합니다.

소비자가 GUID 및 카운터 인덱스가 안정적인 특정 공급자의 데이터만 사용하고 CTRPP 매개 변수에서 CTRPP-ch 생성 기호 파일에 액세스할 수 있는 경우 카운터셋 GUID 및 카운터 인덱스 값을 소비자로 하드 코딩할 수 있습니다.

그렇지 않으면 카운터 세트 메타데이터를 로드하여 다음과 같이 쿼리에 사용할 카운터 세트 GUID 및 카운터 인덱스를 결정해야 합니다.

  • PerfEnumerateCounterSet를 사용하여 지원되는 카운터 세트 GUID 목록을 가져옵니다.
  • 각 GUID에 대해 PerfQueryCounterSetRegistrationInfo 를 사용하여 카운터 세트 이름을 로드합니다. 찾으려는 이름을 찾으면 중지합니다.
  • PerfQueryCounterSetRegistrationInfo를 사용하여 해당 카운터 세트에 대한 나머지 메타데이터(카운터 세트 구성, 카운터 이름, 카운터 인덱스, 카운터 형식)를 로드합니다.

카운터 세트의 현재 활성 인스턴스 이름만 알아야 하는 경우(즉, 실제 성능 데이터 값이 필요하지 않은 경우) PerfEnumerateCounterSetInstances를 사용할 수 있습니다. 카운터 세트 GUID를 입력으로 사용하고 지정된 카운터 세트의 현재 활성 인스턴스의 이름과 ID가 있는 블록을 반환 PERF_INSTANCE_HEADER 합니다.

쿼리

성능 데이터를 수집하려면 쿼리 핸들을 만들고, 쿼리를 추가하고, 쿼리에서 데이터를 수집해야 합니다.

쿼리 핸들에는 여러 쿼리가 연결되어 있을 수 있습니다. 쿼리 핸들에 대해 PerfQueryCounterData를 호출하면 PerfLib은 핸들의 모든 쿼리를 실행하고 모든 결과를 수집합니다.

각 쿼리는 카운터 세트 GUID, instance 이름 필터, 선택적 instance ID 필터 및 선택적 카운터 ID 필터를 지정합니다.

  • 카운터 세트 GUID가 필요합니다. 쿼리에서 데이터를 수집할 카운터 세트의 GUID를 나타냅니다. 이는 SQL 쿼리의 FROM 절과 비슷합니다.
  • instance 이름 필터가 필요합니다. 쿼리에 포함 * 할 instance "모든 문자"를 나타내고 ? "한 문자"를 나타내는 instance 이름과 일치해야 하는 와일드카드 패턴을 나타냅니다. 단일 instance 카운터 세트의 경우 길이가 0인 문자열 ""로 설정해야 합니다. 다중 instance 카운터 세트의 경우 비어 있지 않은 문자열로 설정해야 합니다(모든 instance 이름을 수락하는 데 사용"*"). 이는 SQL 쿼리의 WHERE InstanceName LIKE NameFilter 절과 비슷합니다.
  • instance ID 필터는 선택 사항입니다. 가 있는 경우(즉, 이외의 0xFFFFFFFF값으로 설정된 경우) 쿼리는 instance ID가 지정된 ID와 일치하는 인스턴스만 수집해야 임을 나타냅니다. 이 없는 경우(즉, 로 설정된 0xFFFFFFFF경우) 쿼리가 모든 instance ID를 수락해야 했음을 나타냅니다. 이는 SQL 쿼리의 WHERE InstanceId == IdFilter 절과 비슷합니다.
  • 카운터 ID 필터는 선택 사항입니다. 가 있는 경우(즉, 이외의 PERF_WILDCARD_COUNTER값으로 설정된 경우) 쿼리가 SQL 쿼리의 절과 유사한 단일 카운터를 SELECT CounterName 수집해야 했음을 나타냅니다. 없는 경우(즉, 로 설정된 PERF_WILDCARD_COUNTER경우)는 쿼리가 SQL 쿼리의 절과 유사하게 사용 가능한 모든 카운터를 SELECT * 수집해야 했음을 나타냅니다.

PerfAddCounters를 사용하여 쿼리 핸들에 쿼리를 추가합니다. PerfDeleteCounters를 사용하여 쿼리 핸들에서 쿼리를 제거합니다.

쿼리 핸들에서 쿼리를 변경한 후 PerfQueryCounterInfo 를 사용하여 쿼리 인덱스를 가져옵니다. 인덱스는 PerfQueryCounterData 에서 쿼리 결과가 반환되는 순서를 나타냅니다(결과가 쿼리가 추가된 순서와 항상 일치하지는 않음).

쿼리 핸들이 준비되면 PerfQueryCounterData 를 사용하여 데이터를 수집합니다. 일반적으로 정기적으로(1초 또는 1분에 한 번) 데이터를 수집한 다음 필요에 따라 데이터를 처리합니다.

참고

성능 카운터는 초당 두 번 이상 수집되도록 설계되지 않았습니다.

PerfQueryCounterData는 타임스탬프가 있는 데이터 헤더와 각 쿼리의 결과를 포함하는 블록 시 PERF_COUNTER_HEADER 퀀스로 구성된 블록을 반환 PERF_DATA_HEADER 합니다.

PERF_COUNTER_HEADER 필드 값으로 표시된 dwType 다양한 종류의 데이터를 포함할 수 있습니다.

  • PERF_ERROR_RETURN - PerfLib은 공급자로부터 유효한 카운터 데이터를 다시 가져올 수 없습니다.
  • PERF_SINGLE_COUNTER- 쿼리는 단일 instance 카운터 세트의 단일 카운터에 대한 것이었습니다. 결과에는 요청된 카운터 값만 포함됩니다.
  • PERF_MULTIPLE_COUNTERS- 쿼리는 단일 instance 카운터 세트의 여러 카운터에 대한 것이었습니다. 결과에는 카운터 값과 각 값을 해당 카운터(즉, 열 머리글)와 일치시키는 정보가 포함됩니다.
  • PERF_MULTIPLE_INSTANCES- 쿼리는 다중 instance 카운터 세트의 단일 카운터에 대한 것이었습니다. 결과에는 instance 정보(예: 행 머리글)와 instance당 하나의 카운터 값이 포함됩니다.
  • PERF_COUNTERSET- 쿼리는 다중 instance 카운터 세트의 여러 카운터에 대한 것이었습니다. 결과에는 instance 정보(예: 행 머리글), 각 instance 카운터 값 및 각 값을 해당 카운터(즉, 열 머리글)와 일치시키는 정보가 포함됩니다.

PerfQueryCounterData에서 반환되는 값은 또는 UINT64 원시 값입니다UINT32. 일반적으로 필요한 형식의 값을 생성하려면 일부 처리가 필요합니다. 필요한 처리는 카운터의 형식에 따라 달라집니다. 많은 카운터 형식에는 동일한 샘플의 "기본" 카운터에서 타임스탬프 또는 값과 같은 완전한 처리를 위한 추가 정보가 필요합니다.

일부 카운터 형식은 이전 샘플의 데이터와 비교할 때만 의미 있는 "델타" 카운터입니다. 예를 들어 형식 PERF_SAMPLE_COUNTER 의 카운터에는 속도(샘플 간격 동안 특정 작업이 초당 발생한 횟수)를 표시할 것으로 예상되는 형식이 지정된 값이 있지만 실제 원시 값은 개수에 불과합니다(특정 작업이 총 발생한 횟수). 서식이 지정된 "rate" 값을 생성하려면 카운터 형식에 해당하는 수식을 적용해야 합니다. 에 대한 PERF_SAMPLE_COUNTER 수식은 (N1 - N0) / ((T1 - T0) / F): 이전 샘플 값에서 현재 샘플의 값을 빼고(샘플 간격 동안 이벤트가 발생한 횟수를 제공) 결과를 샘플 간격의 초 수로 나눕니다(이전 샘플의 타임스탬프에서 현재 샘플의 타임스탬프를 빼고 시간 간격을 초로 변환하기 위해 빈도로 나누어 얻은 값).

원시 값에서 형식이 지정된 값을 계산하는 방법에 대한 자세한 내용은 카운터 값 계산을 참조하세요.

샘플

다음 코드는 PerfLib V2 소비자 함수를 사용하여 "프로세서 정보" 카운터 세트에서 CPU 성능 정보를 읽는 소비자를 구현합니다.

소비자는 다음과 같이 구성됩니다.

  • 클래스는 CpuPerfCounters 성능 데이터를 소비하기 위한 논리를 구현합니다. 쿼리 핸들과 쿼리 결과가 기록되는 데이터 버퍼를 캡슐화합니다.
  • 구조체는 CpuPerfTimestamp 샘플에 대한 타임스탬프 정보를 저장합니다. 데이터가 수집될 때마다 호출자는 단일 CpuPerfTimestamp를 받습니다.
  • 구조체는 CpuPerfData 하나의 CPU에 대한 성능 정보(이름 및 원시 성능 값 instance)를 저장합니다. 데이터가 수집될 때마다 호출자는 (CPU당 하나씩) 배열 CpuPerfData 을 받습니다.

이 샘플에서는 GUID 또는 ID 값을 변경하지 않는 특정 카운터 세트(프로세서 정보)에 최적화되어 있으므로 하드 코딩된 카운터 세트 GUID 및 카운터 ID 값을 사용합니다. 임의 카운터 세트에서 성능 데이터를 읽는 보다 일반적인 클래스는 PerfQueryCounterSetRegistrationInfo 를 사용하여 런타임에 카운터 ID와 카운터 값 간의 매핑을 조회해야 합니다.

간단한 CpuPerfCountersConsumer.cpp 프로그램은 클래스의 값을 CpuPerfCounters 사용하는 방법을 보여줍니다.

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