summaryrefslogtreecommitdiffstats
path: root/src/collectors/windows.plugin/perflib-dump.c
blob: e01813a49ffcdf0d1f0fe752d56180112eea21ae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
// SPDX-License-Identifier: GPL-3.0-or-later

#include "perflib.h"
#include "windows-internals.h"

static const char *getCounterType(DWORD CounterType) {
    switch (CounterType) {
        case PERF_COUNTER_COUNTER:
            return "PERF_COUNTER_COUNTER";

        case PERF_COUNTER_TIMER:
            return "PERF_COUNTER_TIMER";

        case PERF_COUNTER_QUEUELEN_TYPE:
            return "PERF_COUNTER_QUEUELEN_TYPE";

        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
            return "PERF_COUNTER_LARGE_QUEUELEN_TYPE";

        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
            return "PERF_COUNTER_100NS_QUEUELEN_TYPE";

        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
            return "PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE";

        case PERF_COUNTER_BULK_COUNT:
            return "PERF_COUNTER_BULK_COUNT";

        case PERF_COUNTER_TEXT:
            return "PERF_COUNTER_TEXT";

        case PERF_COUNTER_RAWCOUNT:
            return "PERF_COUNTER_RAWCOUNT";

        case PERF_COUNTER_LARGE_RAWCOUNT:
            return "PERF_COUNTER_LARGE_RAWCOUNT";

        case PERF_COUNTER_RAWCOUNT_HEX:
            return "PERF_COUNTER_RAWCOUNT_HEX";

        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            return "PERF_COUNTER_LARGE_RAWCOUNT_HEX";

        case PERF_SAMPLE_FRACTION:
            return "PERF_SAMPLE_FRACTION";

        case PERF_SAMPLE_COUNTER:
            return "PERF_SAMPLE_COUNTER";

        case PERF_COUNTER_NODATA:
            return "PERF_COUNTER_NODATA";

        case PERF_COUNTER_TIMER_INV:
            return "PERF_COUNTER_TIMER_INV";

        case PERF_SAMPLE_BASE:
            return "PERF_SAMPLE_BASE";

        case PERF_AVERAGE_TIMER:
            return "PERF_AVERAGE_TIMER";

        case PERF_AVERAGE_BASE:
            return "PERF_AVERAGE_BASE";

        case PERF_AVERAGE_BULK:
            return "PERF_AVERAGE_BULK";

        case PERF_OBJ_TIME_TIMER:
            return "PERF_OBJ_TIME_TIMER";

        case PERF_100NSEC_TIMER:
            return "PERF_100NSEC_TIMER";

        case PERF_100NSEC_TIMER_INV:
            return "PERF_100NSEC_TIMER_INV";

        case PERF_COUNTER_MULTI_TIMER:
            return "PERF_COUNTER_MULTI_TIMER";

        case PERF_COUNTER_MULTI_TIMER_INV:
            return "PERF_COUNTER_MULTI_TIMER_INV";

        case PERF_COUNTER_MULTI_BASE:
            return "PERF_COUNTER_MULTI_BASE";

        case PERF_100NSEC_MULTI_TIMER:
            return "PERF_100NSEC_MULTI_TIMER";

        case PERF_100NSEC_MULTI_TIMER_INV:
            return "PERF_100NSEC_MULTI_TIMER_INV";

        case PERF_RAW_FRACTION:
            return "PERF_RAW_FRACTION";

        case PERF_LARGE_RAW_FRACTION:
            return "PERF_LARGE_RAW_FRACTION";

        case PERF_RAW_BASE:
            return "PERF_RAW_BASE";

        case PERF_LARGE_RAW_BASE:
            return "PERF_LARGE_RAW_BASE";

        case PERF_ELAPSED_TIME:
            return "PERF_ELAPSED_TIME";

        case PERF_COUNTER_HISTOGRAM_TYPE:
            return "PERF_COUNTER_HISTOGRAM_TYPE";

        case PERF_COUNTER_DELTA:
            return "PERF_COUNTER_DELTA";

        case PERF_COUNTER_LARGE_DELTA:
            return "PERF_COUNTER_LARGE_DELTA";

        case PERF_PRECISION_SYSTEM_TIMER:
            return "PERF_PRECISION_SYSTEM_TIMER";

        case PERF_PRECISION_100NS_TIMER:
            return "PERF_PRECISION_100NS_TIMER";

        case PERF_PRECISION_OBJECT_TIMER:
            return "PERF_PRECISION_OBJECT_TIMER";

        default:
            return "UNKNOWN_COUNTER_TYPE";
    }
}

static const char *getCounterDescription(DWORD CounterType) {
    switch (CounterType) {
        case PERF_COUNTER_COUNTER:
            return "32-bit Counter. Divide delta by delta time. Display suffix: \"/sec\"";

        case PERF_COUNTER_TIMER:
            return "64-bit Timer. Divide delta by delta time. Display suffix: \"%\"";

        case PERF_COUNTER_QUEUELEN_TYPE:
        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
            return "Queue Length Space-Time Product. Divide delta by delta time. No Display Suffix";

        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
            return "Queue Length Space-Time Product using 100 Ns timebase. Divide delta by delta time. No Display Suffix";

        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
            return "Queue Length Space-Time Product using Object specific timebase. Divide delta by delta time. No Display Suffix.";

        case PERF_COUNTER_BULK_COUNT:
            return "64-bit Counter.  Divide delta by delta time. Display Suffix: \"/sec\"";

        case PERF_COUNTER_TEXT:
            return "Unicode text Display as text.";

        case PERF_COUNTER_RAWCOUNT:
        case PERF_COUNTER_LARGE_RAWCOUNT:
            return "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix.";

        case PERF_COUNTER_RAWCOUNT_HEX:
        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            return "Special case for RAWCOUNT which should be displayed in hex. A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix.";

        case PERF_SAMPLE_FRACTION:
            return "A count which is either 1 or 0 on each sampling interrupt (% busy). Divide delta by delta base. Display Suffix: \"%\"";

        case PERF_SAMPLE_COUNTER:
            return "A count which is sampled on each sampling interrupt (queue length). Divide delta by delta time. No Display Suffix.";

        case PERF_COUNTER_NODATA:
            return "A label: no data is associated with this counter (it has 0 length). Do not display.";

        case PERF_COUNTER_TIMER_INV:
            return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 - delta divided by delta time.  Display suffix: \"%\"";

        case PERF_SAMPLE_BASE:
            return "The divisor for a sample, used with the previous counter to form a sampled %. You must check for >0 before dividing by this! This counter will directly follow the numerator counter. It should not be displayed to the user.";

        case PERF_AVERAGE_TIMER:
            return "A timer which, when divided by an average base, produces a time in seconds which is the average time of some operation. This timer times total operations, and the base is the number of operations. Display Suffix: \"sec\"";

        case PERF_AVERAGE_BASE:
            return "Used as the denominator in the computation of time or count averages. Must directly follow the numerator counter. Not displayed to the user.";

        case PERF_AVERAGE_BULK:
            return "A bulk count which, when divided (typically) by the number of operations, gives (typically) the number of bytes per operation. No Display Suffix.";

        case PERF_OBJ_TIME_TIMER:
            return "64-bit Timer in object specific units. Display delta divided by delta time as returned in the object type header structure.  Display suffix: \"%\"";

        case PERF_100NSEC_TIMER:
            return "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\"";

        case PERF_100NSEC_TIMER_INV:
            return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 - delta divided by delta time.  Display suffix: \"%\"";

        case PERF_COUNTER_MULTI_TIMER:
            return "64-bit Timer.  Divide delta by delta time.  Display suffix: \"%\". Timer for multiple instances, so result can exceed 100%.";

        case PERF_COUNTER_MULTI_TIMER_INV:
            return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 * _MULTI_BASE - delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%. Followed by a counter of type _MULTI_BASE.";

        case PERF_COUNTER_MULTI_BASE:
            return "Number of instances to which the preceding _MULTI_..._INV counter applies. Used as a factor to get the percentage.";

        case PERF_100NSEC_MULTI_TIMER:
            return "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%.";

        case PERF_100NSEC_MULTI_TIMER_INV:
            return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 * _MULTI_BASE - delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%. Followed by a counter of type _MULTI_BASE.";

        case PERF_LARGE_RAW_FRACTION:
        case PERF_RAW_FRACTION:
            return "Indicates the data is a fraction of the following counter  which should not be time averaged on display (such as free space over total space.) Display as is. Display the quotient as \"%\"";

        case PERF_RAW_BASE:
        case PERF_LARGE_RAW_BASE:
            return "Indicates the data is a base for the preceding counter which should not be time averaged on display (such as free space over total space.)";

        case PERF_ELAPSED_TIME:
            return "The data collected in this counter is actually the start time of the item being measured. For display, this data is subtracted from the sample time to yield the elapsed time as the difference between the two. In the definition below, the PerfTime field of the Object contains the sample time as indicated by the PERF_OBJECT_TIMER bit and the difference is scaled by the PerfFreq of the Object to convert the time units into seconds.";

        case PERF_COUNTER_HISTOGRAM_TYPE:
            return "Counter type can be used with the preceding types to define a range of values to be displayed in a histogram.";

        case PERF_COUNTER_DELTA:
        case PERF_COUNTER_LARGE_DELTA:
            return "This counter is used to display the difference from one sample to the next. The counter value is a constantly increasing number  and the value displayed is the difference between the current value and the previous value. Negative numbers are not allowed which shouldn't be a problem as long as the counter value is increasing or unchanged.";

        case PERF_PRECISION_SYSTEM_TIMER:
            return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used has the same frequency as the System Performance Timer";

        case PERF_PRECISION_100NS_TIMER:
            return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used has the same frequency as the 100 NanoSecond Timer";

        case PERF_PRECISION_OBJECT_TIMER:
            return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used is of the frequency specified in the Object header's. PerfFreq field (PerfTime is ignored)";

        default:
            return "";
    }
}

static const char *getCounterAlgorithm(DWORD CounterType) {
    switch (CounterType)
    {
        case PERF_COUNTER_COUNTER:
        case PERF_SAMPLE_COUNTER:
        case PERF_COUNTER_BULK_COUNT:
            return "(data1 - data0) / ((time1 - time0) / frequency)";

        case PERF_COUNTER_QUEUELEN_TYPE:
        case PERF_COUNTER_100NS_QUEUELEN_TYPE:
        case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE:
        case PERF_COUNTER_LARGE_QUEUELEN_TYPE:
        case PERF_AVERAGE_BULK:  // normally not displayed
            return "(data1 - data0) / (time1 - time0)";

        case PERF_OBJ_TIME_TIMER:
        case PERF_COUNTER_TIMER:
        case PERF_100NSEC_TIMER:
        case PERF_PRECISION_SYSTEM_TIMER:
        case PERF_PRECISION_100NS_TIMER:
        case PERF_PRECISION_OBJECT_TIMER:
        case PERF_SAMPLE_FRACTION:
            return "100 * (data1 - data0) / (time1 - time0)";

        case PERF_COUNTER_TIMER_INV:
            return "100 * (1 - ((data1 - data0) / (time1 - time0)))";

        case PERF_100NSEC_TIMER_INV:
            return "100 * (1- (data1 - data0) / (time1 - time0))";

        case PERF_COUNTER_MULTI_TIMER:
            return "100 * ((data1 - data0) / ((time1 - time0) / frequency1)) / multi1";

        case PERF_100NSEC_MULTI_TIMER:
            return "100 * ((data1 - data0) / (time1 - time0)) / multi1";

        case PERF_COUNTER_MULTI_TIMER_INV:
        case PERF_100NSEC_MULTI_TIMER_INV:
            return "100 * (multi1 - ((data1 - data0) / (time1 - time0)))";

        case PERF_COUNTER_RAWCOUNT:
        case PERF_COUNTER_LARGE_RAWCOUNT:
            return "data0";

        case PERF_COUNTER_RAWCOUNT_HEX:
        case PERF_COUNTER_LARGE_RAWCOUNT_HEX:
            return "hex(data0)";

        case PERF_COUNTER_DELTA:
        case PERF_COUNTER_LARGE_DELTA:
            return "data1 - data0";

        case PERF_RAW_FRACTION:
        case PERF_LARGE_RAW_FRACTION:
            return "100 * data0 / time0";

        case PERF_AVERAGE_TIMER:
            return "((data1 - data0) / frequency1) / (time1 - time0)";

        case PERF_ELAPSED_TIME:
            return "(time0 - data0) / frequency0";

        case PERF_COUNTER_TEXT:
        case PERF_SAMPLE_BASE:
        case PERF_AVERAGE_BASE:
        case PERF_COUNTER_MULTI_BASE:
        case PERF_RAW_BASE:
        case PERF_COUNTER_NODATA:
        case PERF_PRECISION_TIMESTAMP:
        default:
            return "";
    }
}

void dumpSystemTime(BUFFER *wb, SYSTEMTIME *st) {
    buffer_json_member_add_uint64(wb, "Year", st->wYear);
    buffer_json_member_add_uint64(wb, "Month", st->wMonth);
    buffer_json_member_add_uint64(wb, "DayOfWeek", st->wDayOfWeek);
    buffer_json_member_add_uint64(wb, "Day", st->wDay);
    buffer_json_member_add_uint64(wb, "Hour", st->wHour);
    buffer_json_member_add_uint64(wb, "Minute", st->wMinute);
    buffer_json_member_add_uint64(wb, "Second", st->wSecond);
    buffer_json_member_add_uint64(wb, "Milliseconds", st->wMilliseconds);
}

bool dumpDataCb(PERF_DATA_BLOCK *pDataBlock, void *data) {
    char name[4096];
    if(!getSystemName(pDataBlock, name, sizeof(name)))
        strncpyz(name, "[failed]", sizeof(name) - 1);

    BUFFER *wb = data;
    buffer_json_member_add_string(wb, "SystemName", name);

    // Number of types of objects being reported
    // Type: DWORD
    buffer_json_member_add_int64(wb, "NumObjectTypes", pDataBlock->NumObjectTypes);

    buffer_json_member_add_int64(wb, "LittleEndian", pDataBlock->LittleEndian);

    // Version and Revision of these data structures.
    // Version starts at 1.
    // Revision starts at 0 for each Version.
    // Type: DWORD
    buffer_json_member_add_int64(wb, "Version", pDataBlock->Version);
    buffer_json_member_add_int64(wb, "Revision", pDataBlock->Revision);

    // Object Title Index of default object to display when data from this system is retrieved
    // (-1 = none, but this is not expected to be used)
    // Type: LONG
    buffer_json_member_add_int64(wb, "DefaultObject", pDataBlock->DefaultObject);

    // Performance counter frequency at the system under measurement
    // Type: LARGE_INTEGER
    buffer_json_member_add_int64(wb, "PerfFreq", pDataBlock->PerfFreq.QuadPart);

    // Performance counter value at the system under measurement
    // Type: LARGE_INTEGER
    buffer_json_member_add_int64(wb, "PerfTime", pDataBlock->PerfTime.QuadPart);

    // Performance counter time in 100 nsec units at the system under measurement
    // Type: LARGE_INTEGER
    buffer_json_member_add_int64(wb, "PerfTime100nSec", pDataBlock->PerfTime100nSec.QuadPart);

    // Time at the system under measurement in UTC
    // Type: SYSTEMTIME
    buffer_json_member_add_object(wb, "SystemTime");
    dumpSystemTime(wb, &pDataBlock->SystemTime);
    buffer_json_object_close(wb);

    if(pDataBlock->NumObjectTypes)
        buffer_json_member_add_array(wb, "Objects");

    return true;
}

static const char *GetDetailLevel(DWORD num) {
    switch (num) {
        case 100:
            return "Novice (100)";
        case 200:
            return "Advanced (200)";
        case 300:
            return "Expert (300)";
        case 400:
            return "Wizard (400)";

        default:
            return "Unknown";
    }
}

bool dumpObjectCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, void *data) {
    (void)pDataBlock;
    BUFFER *wb = data;
    if(!pObjectType) {
        buffer_json_array_close(wb); // instances or counters
        buffer_json_object_close(wb); // objectType
        return true;
    }

    buffer_json_add_array_item_object(wb); // objectType
    buffer_json_member_add_int64(wb, "NameId", pObjectType->ObjectNameTitleIndex);
    buffer_json_member_add_string(wb, "Name", RegistryFindNameByID(pObjectType->ObjectNameTitleIndex));
    buffer_json_member_add_int64(wb, "HelpId", pObjectType->ObjectHelpTitleIndex);
    buffer_json_member_add_string(wb, "Help", RegistryFindHelpByID(pObjectType->ObjectHelpTitleIndex));
    buffer_json_member_add_int64(wb, "NumInstances", pObjectType->NumInstances);
    buffer_json_member_add_int64(wb, "NumCounters", pObjectType->NumCounters);
    buffer_json_member_add_int64(wb, "PerfTime", pObjectType->PerfTime.QuadPart);
    buffer_json_member_add_int64(wb, "PerfFreq", pObjectType->PerfFreq.QuadPart);
    buffer_json_member_add_int64(wb, "CodePage", pObjectType->CodePage);
    buffer_json_member_add_int64(wb, "DefaultCounter", pObjectType->DefaultCounter);
    buffer_json_member_add_string(wb, "DetailLevel", GetDetailLevel(pObjectType->DetailLevel));

    if(ObjectTypeHasInstances(pDataBlock, pObjectType))
        buffer_json_member_add_array(wb, "Instances");
    else
        buffer_json_member_add_array(wb, "Counters");

    return true;
}

bool dumpInstanceCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, void *data) {
    (void)pDataBlock;
    BUFFER *wb = data;
    if(!pInstance) {
        buffer_json_array_close(wb); // counters
        buffer_json_object_close(wb); // instance
        return true;
    }

    char name[4096];
    if(!getInstanceName(pDataBlock, pObjectType, pInstance, name, sizeof(name)))
        strncpyz(name, "[failed]", sizeof(name) - 1);

    buffer_json_add_array_item_object(wb);
    buffer_json_member_add_string(wb, "Instance", name);
    buffer_json_member_add_int64(wb, "UniqueID", pInstance->UniqueID);
    buffer_json_member_add_array(wb, "Labels");
    {
        buffer_json_add_array_item_object(wb);
        {
            buffer_json_member_add_string(wb, "key", RegistryFindNameByID(pObjectType->ObjectNameTitleIndex));
            buffer_json_member_add_string(wb, "value", name);
        }
        buffer_json_object_close(wb);

        if(pInstance->ParentObjectTitleIndex) {
            PERF_INSTANCE_DEFINITION *pi = pInstance;
            while(pi->ParentObjectTitleIndex) {
                PERF_OBJECT_TYPE *po = getObjectTypeByIndex(pDataBlock, pInstance->ParentObjectTitleIndex);
                pi = getInstanceByPosition(pDataBlock, po, pi->ParentObjectInstance);

                if(!getInstanceName(pDataBlock, po, pi, name, sizeof(name)))
                    strncpyz(name, "[failed]", sizeof(name) - 1);

                buffer_json_add_array_item_object(wb);
                {
                    buffer_json_member_add_string(wb, "key", RegistryFindNameByID(po->ObjectNameTitleIndex));
                    buffer_json_member_add_string(wb, "value", name);
                }
                buffer_json_object_close(wb);
            }
        }
    }
    buffer_json_array_close(wb); // rrdlabels

    buffer_json_member_add_array(wb, "Counters");
    return true;
}

void dumpSample(BUFFER *wb, RAW_DATA *d) {
    buffer_json_member_add_object(wb, "Value");
    buffer_json_member_add_uint64(wb, "data", d->Data);
    buffer_json_member_add_int64(wb, "time", d->Time);
    buffer_json_member_add_uint64(wb, "type", d->CounterType);
    buffer_json_member_add_int64(wb, "multi", d->MultiCounterData);
    buffer_json_member_add_int64(wb, "frequency", d->Frequency);
    buffer_json_object_close(wb);
}

bool dumpCounterCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data) {
    (void)pDataBlock;
    (void)pObjectType;
    BUFFER *wb = data;
    buffer_json_add_array_item_object(wb);
    buffer_json_member_add_string(wb, "Counter", RegistryFindNameByID(pCounter->CounterNameTitleIndex));
    dumpSample(wb, sample);
    buffer_json_member_add_string(wb, "Help", RegistryFindHelpByID(pCounter->CounterHelpTitleIndex));
    buffer_json_member_add_string(wb, "Type", getCounterType(pCounter->CounterType));
    buffer_json_member_add_string(wb, "Algorithm", getCounterAlgorithm(pCounter->CounterType));
    buffer_json_member_add_string(wb, "Description", getCounterDescription(pCounter->CounterType));
    buffer_json_object_close(wb);
    return true;
}

bool dumpInstanceCounterCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data) {
    (void)pInstance;
    return dumpCounterCb(pDataBlock, pObjectType, pCounter, sample, data);
}


int windows_perflib_dump(const char *key) {
    if(key && !*key)
        key = NULL;

    PerflibNamesRegistryInitialize();

    DWORD id = 0;
    if(key) {
        id = RegistryFindIDByName(key);
        if(id == PERFLIB_REGISTRY_NAME_NOT_FOUND) {
            fprintf(stderr, "Cannot find key '%s' in Windows Performance Counters Registry.\n", key);
            exit(1);
        }
    }

    CLEAN_BUFFER *wb = buffer_create(0, NULL);
    buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY);

    perflibQueryAndTraverse(id, dumpDataCb, dumpObjectCb, dumpInstanceCb, dumpInstanceCounterCb, dumpCounterCb, wb);

    buffer_json_finalize(wb);
    printf("\n%s\n", buffer_tostring(wb));

    perflibFreePerformanceData();

    return 0;
}