Bagikan melalui


Menggunakan Fungsi PerfLib untuk Mengonsumsi Data Penghitung

Gunakan fungsi Konsumen PerfLib untuk menggunakan data performa dari penyedia data performa V2 saat Anda tidak dapat menggunakan fungsi Pembantu Data Performa (PDH). Fungsi-fungsi ini dapat digunakan saat menulis OneCore aplikasi untuk mengumpulkan counterset V2 atau ketika Anda perlu mengumpulkan counterset V2 dengan dependensi dan overhead minimal.

Tip

Fungsi Konsumen PerfLib V2 lebih sulit digunakan daripada fungsi Performance Data Helper (PDH) dan hanya mendukung pengumpulan data dari penyedia V2. Fungsi PDH harus lebih disukai untuk sebagian besar aplikasi.

Fungsi Konsumen PerfLib V2 adalah API tingkat rendah untuk mengumpulkan data dari penyedia V2. Fungsi Konsumen PerfLib V2 tidak mendukung pengumpulan data dari penyedia V1.

Peringatan

Fungsi konsumen PerfLib V2 berpotensi mengumpulkan data dari sumber yang tidak tepercaya, misalnya dari layanan lokal hak istimewa terbatas atau dari komputer jarak jauh. Fungsi konsumen PerfLib V2 tidak memvalidasi data untuk integritas atau konsistensi. Terserah aplikasi konsumen Anda untuk memverifikasi bahwa data yang dikembalikan konsisten, misalnya nilai Ukuran di blok data yang dikembalikan tidak melebihi ukuran aktual dari blok data yang dikembalikan. Ini sangat penting ketika aplikasi konsumen berjalan pada hak istimewa yang ditinggikan.

Penggunaan PerfLib

Header perflib.h mencakup deklarasi yang digunakan oleh penyedia mode pengguna V2 (yaitu PERFLib Provider API) dan konsumen V2 (yaitu PerfLib Consumer API). Ini mendeklarasikan fungsi berikut untuk mengonsumsi data performa V2:

Jika konsumen Anda akan mengonsumsi data hanya dari penyedia tertentu di mana INDEKS GUID dan penghitung stabil dan Anda memiliki akses ke file simbol yang dihasilkan CTRPP (dari parameter CTRPP -ch ), Anda dapat mengkodekan secara permanen GUID penghitung dan penghitung nilai indeks ke konsumen Anda.

Jika tidak, Anda harus memuat metadata penghitung untuk menentukan GUID penghitung dan indeks penghitung yang akan digunakan dalam kueri Anda sebagai berikut:

  • Gunakan PerfEnumerateCounterSet untuk mendapatkan daftar GUID penghitung yang didukung.
  • Untuk setiap GUID, gunakan PerfQueryCounterSetRegistrationInfo untuk memuat nama counterset. Berhenti ketika Anda menemukan nama yang Anda cari.
  • Gunakan PerfQueryCounterSetRegistrationInfo untuk memuat metadata yang tersisa (konfigurasi penghitung, nama penghitung, indeks penghitung, jenis penghitung) untuk penghitung tersebut.

Jika Anda hanya perlu mengetahui nama instans yang saat ini aktif dari counterset (yaitu jika Anda tidak memerlukan nilai data performa aktual), Anda dapat menggunakan PerfEnumerateCounterSetInstances. Ini mengambil GUID counterset sebagai input dan mengembalikan PERF_INSTANCE_HEADER blok dengan nama dan ID instans yang saat ini aktif dari counterset yang diberikan.

Kueri

Untuk mengumpulkan data performa, Anda perlu membuat handel kueri, menambahkan kueri ke dalamnya, dan mengumpulkan data dari kueri.

Handel kueri dapat memiliki banyak kueri yang terkait dengannya. Saat Anda memanggil PerfQueryCounterData untuk handel kueri, PerfLib akan menjalankan semua kueri handel dan mengumpulkan semua hasilnya.

Setiap kueri menentukan GUID counterset, filter nama instans, filter ID instans opsional, dan filter ID penghitung opsional.

  • GUID counterset diperlukan. Ini menunjukkan GUID dari counterset tempat kueri akan mengumpulkan data. Ini mirip FROM dengan klausa kueri SQL.
  • Filter nama instans diperlukan. Ini menunjukkan pola kartubebas yang harus dicocokkan dengan nama instans agar instans disertakan dalam kueri, dengan * menunjukkan "karakter apa pun" dan ? menunjukkan "satu karakter". Untuk penghitung instans tunggal, ini HARUS diatur ke string ""panjang nol . Untuk penghitung multi-instans, ini HARUS diatur ke string yang tidak kosong (gunakan "*" untuk menerima semua nama instans). Ini mirip WHERE InstanceName LIKE NameFilter dengan klausa kueri SQL.
  • Filter ID instans bersifat opsional. Jika ada (yaitu jika diatur ke nilai selain 0xFFFFFFFF), itu menunjukkan bahwa kueri hanya boleh mengumpulkan instans di mana ID instans cocok dengan ID yang ditentukan. Jika tidak ada (yaitu jika diatur ke 0xFFFFFFFF), itu menunjukkan bahwa kueri harus menerima semua ID instans. Ini mirip WHERE InstanceId == IdFilter dengan klausa kueri SQL.
  • Filter ID penghitung bersifat opsional. Jika ada (yaitu jika diatur ke nilai selain PERF_WILDCARD_COUNTER), itu menunjukkan bahwa kueri harus mengumpulkan penghitung tunggal, mirip SELECT CounterName dengan klausul kueri SQL. Jika tidak ada (yaitu jika diatur ke PERF_WILDCARD_COUNTER), itu menunjukkan bahwa kueri harus mengumpulkan semua penghitung yang SELECT * tersedia, mirip dengan klausul kueri SQL.

Gunakan PerfAddCounters untuk menambahkan kueri ke handel kueri. Gunakan PerfDeleteCounters untuk menghapus kueri dari handel kueri.

Setelah mengubah kueri dalam handel kueri, gunakan PerfQueryCounterInfo untuk mendapatkan indeks kueri. Indeks menunjukkan urutan di mana hasil kueri akan dikembalikan oleh PerfQueryCounterData (hasilnya tidak akan selalu cocok dengan urutan di mana kueri ditambahkan).

Setelah handel kueri siap, gunakan PerfQueryCounterData untuk mengumpulkan data. Anda biasanya akan mengumpulkan data secara berkala (sekali sedetik atau sekali dalam satu menit) lalu memproses data sesuai kebutuhan.

Catatan

Penghitung kinerja tidak dirancang untuk dikumpulkan lebih dari sekali per detik.

PerfQueryCounterData akan mengembalikan PERF_DATA_HEADER blok, yang terdiri dari header data dengan tanda waktu diikuti dengan urutan PERF_COUNTER_HEADER blok, masing-masing berisi hasil satu kueri.

PERF_COUNTER_HEADER mungkin berisi berbagai jenis data yang berbeda, seperti yang ditunjukkan oleh nilai dwType bidang :

  • PERF_ERROR_RETURN - PerfLib tidak dapat memperoleh kembali data penghitung yang valid dari penyedia.
  • PERF_SINGLE_COUNTER - Kueri ini untuk penghitung tunggal dari satu set penghitung instans. Hasilnya hanya berisi nilai penghitung yang diminta.
  • PERF_MULTIPLE_COUNTERS - Kueri ini untuk beberapa penghitung dari satu set penghitung instans. Hasilnya berisi nilai penghitung bersama dengan informasi untuk mencocokkan setiap nilai dengan penghitung yang sesuai (yaitu judul kolom).
  • PERF_MULTIPLE_INSTANCES - Kueri ini untuk penghitung tunggal dari penghitung multi-instans. Hasilnya berisi informasi instans (yaitu judul baris) dan satu nilai penghitung per instans.
  • PERF_COUNTERSET - Kueri ini untuk beberapa penghitung dari counterset multi-instans. Hasilnya berisi informasi instans (yaitu judul baris), nilai penghitung untuk setiap instans, dan informasi untuk mencocokkan setiap nilai dengan penghitung yang sesuai (yaitu judul kolom).

Nilai yang dikembalikan oleh PerfQueryCounterData adalah UINT32 nilai mentah atau UINT64 . Ini biasanya memerlukan beberapa pemrosesan untuk menghasilkan nilai yang diformat yang diharapkan. Pemrosesan yang diperlukan tergantung pada jenis penghitung. Banyak jenis penghitung memerlukan informasi tambahan untuk pemrosesan lengkap, seperti tanda waktu atau nilai dari penghitung "dasar" dalam sampel yang sama.

Beberapa jenis penghitung adalah penghitung "delta" yang hanya bermakna jika dibandingkan dengan data dari sampel sebelumnya. Misalnya, penghitung jenis PERF_SAMPLE_COUNTER memiliki nilai yang diformat yang diharapkan menunjukkan laju (berapa kali hal tertentu terjadi per detik selama interval sampel), tetapi nilai mentah aktual hanya hitungan (berapa kali hal tertentu telah terjadi secara total). Untuk menghasilkan nilai "laju" yang diformat, Anda harus menerapkan rumus yang sesuai dengan jenis penghitung. Rumus untuk PERF_SAMPLE_COUNTER adalah (N1 - N0) / ((T1 - T0) / F): mengurangi nilai sampel saat ini dari nilai sampel sebelumnya (memberikan berapa kali peristiwa terjadi selama interval sampel) lalu membagi hasil dengan jumlah detik dalam interval sampel (diperoleh dengan mengurangi tanda waktu sampel saat ini dari tanda waktu sampel sebelumnya dan membagi berdasarkan frekuensi untuk mengonversi rentang waktu menjadi detik).

Lihat Menghitung Nilai Penghitung untuk informasi selengkapnya tentang menghitung nilai yang diformat dari nilai mentah.

Sampel

Kode berikut mengimplementasikan konsumen yang menggunakan fungsi Konsumen PerfLib V2 untuk membaca informasi performa CPU dari counterset "Informasi Prosesor".

Konsumen diatur sebagai berikut:

  • Kelas CpuPerfCounters mengimplementasikan logika untuk mengonsumsi data performa. Ini merangkum handel kueri dan buffer data tempat hasil kueri direkam.
  • Struktur CpuPerfTimestamp menyimpan informasi tanda waktu untuk sampel. Setiap kali data dikumpulkan, pemanggil menerima satu CpuPerfTimestamp.
  • Struktur CpuPerfData menyimpan informasi performa (nama instans dan nilai performa mentah) untuk satu CPU. Setiap kali data dikumpulkan, pemanggil menerima array CpuPerfData (satu per CPU).

Sampel ini menggunakan nilai GUID dan ID penghitung penghitung penghitung yang dikodekan secara permanen karena dioptimalkan untuk penghitung tertentu (Informasi Prosesor) yang tidak akan mengubah nilai GUID atau ID. Kelas yang lebih umum yang membaca data performa dari counterset arbitrer perlu menggunakan PerfQueryCounterSetRegistrationInfo untuk mencari pemetaan antara ID penghitung dan nilai penghitung saat runtime.

Program sederhana CpuPerfCountersConsumer.cpp menunjukkan cara menggunakan nilai dari CpuPerfCounters kelas .

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