1/*
2    Title:  PolyPerf.cpp
3
4    Copyright (c) 2011, 2019 David C.J. Matthews
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License version 2.1 as published by the Free Software Foundation.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19*/
20
21#include <windows.h>
22#include <winperf.h>
23#include <stdlib.h>
24#include <malloc.h>
25#include <psapi.h>
26#include <stdio.h>
27#include <string.h>
28#include <stddef.h>
29
30#include "../polystatistics.h"
31
32// Statistics currently provided.  These are copied from statistics.cpp
33// although the statistics returned may not match.
34enum {
35    PSC_THREADS = 0,                // Total number of threads
36    PSC_THREADS_IN_ML,              // Threads running ML code
37    PSC_THREADS_WAIT_IO,            // Threads waiting for IO
38    PSC_THREADS_WAIT_MUTEX,         // Threads waiting for a mutex
39    PSC_THREADS_WAIT_CONDVAR,       // Threads waiting for a condition var
40    PSC_THREADS_WAIT_SIGNAL,        // Special case - signal handling thread
41    PSC_GC_FULLGC,                  // Number of full garbage collections
42    PSC_GC_PARTIALGC,               // Number of partial GCs
43    PSC_GC_SHARING,                 // Number of sharing passes
44    N_PS_COUNTERS
45};
46
47enum {
48    PSS_TOTAL_HEAP = 0,                 // Total size of the local heap
49    PSS_AFTER_LAST_GC,              // Space free after last GC
50    PSS_AFTER_LAST_FULLGC,          // Space free after the last full GC
51    PSS_ALLOCATION,                 // Size of allocation space
52    PSS_ALLOCATION_FREE,            // Space available in allocation area
53//    PSS_CODE_SPACE,                 // Space for code
54//    PSS_STACK_SPACE,                // Space for stack
55    N_PS_SIZES
56};
57
58enum {
59    PST_NONGC_UTIME = 0,
60    PST_NONGC_STIME,
61    PST_GC_UTIME,
62    PST_GC_STIME,
63    PST_NONGC_RTIME,
64    PST_GC_RTIME,
65    N_PS_TIMES
66};
67
68#define N_PS_USER   8
69
70/*
71This DLL is a plug-in for Windows performance monitoring.  The whole
72interface is extremely messy and seems to have remained unchanged
73from NT 3.5.  The localised string names displayed in the performance
74monitor are held in the registry and need to be set up before this
75DLL can be loaded.  Wix v3 supports the strings directly and this
76replaces the old method of using lodctr with .ini and .h files.
77The DLL is loaded by the performance monitor.  In XP that seems to
78be part of the management console application mmc.exe and perfmon.exe
79seems to be just a stub.
80*/
81
82extern "C" {
83    /* These are the functions exported from the DLL.  The names here appear in the
84       HKLM\SYSTEM\CurrentControlSet\Services\PolyML\Performance registry key.
85       This DLL is loaded by mmc.exe and these functions are called to begin and
86       end monitoring and to extract the current performance values from the
87       shared memory. */
88    __declspec(dllexport) PM_OPEN_PROC OpenPolyPerfMon;
89    __declspec(dllexport) PM_CLOSE_PROC ClosePolyPerfMon;
90    __declspec(dllexport) PM_COLLECT_PROC CollectPolyPerfMon;
91};
92
93// Export the functions without any decoration.
94#ifndef _WIN64
95#pragma comment(linker, "/export:OpenPolyPerfMon=_OpenPolyPerfMon@4")
96#pragma comment(linker, "/export:ClosePolyPerfMon=_ClosePolyPerfMon@0")
97#pragma comment(linker, "/export:CollectPolyPerfMon=_CollectPolyPerfMon@16")
98#endif
99
100class PolyProcess {
101public:
102    ~PolyProcess();
103    static PolyProcess* CreateProcessEntry(DWORD pID);
104
105    PolyProcess();
106
107    unsigned char *sharedMem; // Pointer to shared memory
108    DWORD processID; // Process ID
109    WCHAR *processName; // Unicode name
110};
111
112// This is the structure of the decoded statistics.  These values
113// are set from the ASN1 coding.
114// The types of the fields must match the types set in the registry
115// by the installer (PolyML.wxs).  For counters that is numberOfItems32
116// and for sizes they have to be DWORDs.  The information we display
117// for sizes is always a ratio of two sizes (i.e. % full) so we
118// have to scale the values consistently to get them to fit.
119// Since we also provide type information in the PERF_COUNTER_DEFINITION
120// structure it's possible we may be able to override that but it's
121// not clear.
122typedef struct {
123    PERF_COUNTER_BLOCK header;
124    // Statistics decoded
125    UINT32 psCounters[N_PS_COUNTERS];       // numberOfItems32
126#define SIZEOF_COUNTER  (sizeof(UINT32))
127    DWORD psSizes[N_PS_SIZES];              // rawFraction/rawBase (i.e. 32-bits)
128#define SIZEOF_SIZE     (sizeof(DWORD))
129    FILETIME psTimers[N_PS_TIMES];          // timer100Ns
130#define SIZEOF_TIME     (sizeof(FILETIME))
131    UINT32 psUser[N_PS_USER];               // numberOfItems32
132#define SIZEOF_USER     (sizeof(UINT32))
133} statistics;
134
135PolyProcess::PolyProcess()
136{
137    sharedMem = NULL;
138    processName = NULL;
139}
140
141PolyProcess::~PolyProcess()
142{
143    if (sharedMem)
144        ::UnmapViewOfFile(sharedMem);
145    free(processName);
146}
147
148// Try to open the shared memory and if it succeeds create an entry for
149// this process.
150PolyProcess *PolyProcess::CreateProcessEntry(DWORD pId)
151{
152    char shmName[MAX_PATH];
153    sprintf(shmName, POLY_STATS_NAME "%lu", pId);
154    HANDLE hRemMemory = OpenFileMapping(FILE_MAP_READ, FALSE, shmName);
155    if (hRemMemory == NULL)
156        return NULL; // Probably not a Poly/ML process
157
158    unsigned char *sMem = (unsigned char*)MapViewOfFile(hRemMemory, FILE_MAP_READ, 0, 0, 0);
159    CloseHandle(hRemMemory); // We don't need this whether it succeeded or not
160    if (sMem == NULL)
161        return NULL;
162    if (*sMem != POLY_STATS_C_STATISTICS)
163    {
164        UnmapViewOfFile(sMem);
165        return NULL;
166    }
167    // Looks good.
168    PolyProcess *result = new PolyProcess;
169    result->processID = pId;
170    result->sharedMem = sMem;
171
172    // Find the name of the process.
173    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pId);
174    if (hProcess != NULL)
175    {
176        HMODULE hMod;
177        DWORD cbNeeded;
178        if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),  &cbNeeded))
179        {
180            WCHAR processName[MAX_PATH];
181            size_t len = 0;
182            processName[0] = 0;
183            if (GetModuleBaseNameW(hProcess, hMod, processName, sizeof(processName)/sizeof(WCHAR)) != 0)
184            {
185                // Remove any ".exe" or similar at the end
186                len = wcslen(processName);
187                if (len > 4 && processName[len-4] == '.')
188                    len -= 4;
189                processName[len] = 0;
190            }
191            // Add the process Id in the name
192            _snwprintf(processName+len, MAX_PATH-len, L" (%lu)", pId);
193            // Copy it into the heap
194            result->processName = _wcsdup(processName);
195        }
196        CloseHandle(hProcess);
197    }
198
199    return result;
200}
201
202class ASN1Parse {
203public:
204    ASN1Parse (statistics *s, unsigned char *p): stats(s), ptr(p) {}
205    void asn1Decode();
206    unsigned getLength();
207    INT64 parseInt(unsigned length);
208    UINT32 parseUnsigned(unsigned length);
209    DWORD parseSize(unsigned length);
210    void parseAStatistic(int subTag, unsigned statlen);
211    void parseTime(FILETIME *ft, unsigned length);
212
213    statistics *stats;
214    unsigned char *ptr;
215};
216
217// Decode the ASN1 encoding.  If the decoding fails we just leave the
218// values as zero rather than returning any error.
219void ASN1Parse::asn1Decode()
220{
221    unsigned char ch = *ptr++;
222    if (ch != POLY_STATS_C_STATISTICS) return;
223    unsigned overallLength = getLength();
224    unsigned char *endOfData = ptr+overallLength;
225    while (ptr < endOfData)
226    {
227        // Decode a statistic
228        unsigned tag = *ptr++;
229        unsigned statLen = getLength();
230        switch (tag)
231        {
232        case POLY_STATS_C_COUNTERSTAT:
233            parseAStatistic(POLY_STATS_C_COUNTER_VALUE, statLen);
234            break;
235        case POLY_STATS_C_SIZESTAT:
236            parseAStatistic(POLY_STATS_C_BYTE_COUNT, statLen);
237            break;
238        case POLY_STATS_C_TIMESTAT:
239            parseAStatistic(POLY_STATS_C_TIME, statLen);
240            break;
241        case POLY_STATS_C_USERSTAT:
242            parseAStatistic(POLY_STATS_C_COUNTER_VALUE, statLen);
243            break;
244        default: ptr += statLen; // Skip it; it's not known
245        }
246    }
247}
248
249// Return the length of the next item
250unsigned ASN1Parse::getLength()
251{
252    unsigned ch = *ptr++;
253    if (ch & 0x80)
254    {
255        int lengthOfLength = ch & 0x7f;
256        unsigned length = 0;
257        // Ignore "indefinite length", it's not used here.
258        while (lengthOfLength--)
259        {
260            ch = *ptr++;
261            length = (length << 8) | ch;
262        }
263        return length;
264    }
265    else return ch;
266}
267
268// General case for integer.
269INT64 ASN1Parse::parseInt(unsigned length)
270{
271    if (length == 0) return 0;
272    INT64 result = *ptr & 0x80 ? -1 : 0;
273    while (length--) result = (result << 8) | *ptr++;
274    return result;
275}
276
277UINT32 ASN1Parse::parseUnsigned(unsigned length)
278{
279    INT64 value = parseInt(length);
280    if (value < 0) return 0; // Can't display negative nos
281    return (UINT32)value;
282}
283
284DWORD ASN1Parse::parseSize(unsigned length)
285{
286    INT64 value = parseInt(length);
287    if (value < 0) return 0; // Can't display negative nos
288    return (DWORD)(value / 1024); // Return kilobytes
289}
290
291void ASN1Parse::parseTime(FILETIME *ft, unsigned length)
292{
293    unsigned char *end = ptr+length;
294    UINT32 seconds = 0, useconds = 0;
295    while (ptr < end)
296    {
297        unsigned char tag = *ptr++;
298        unsigned elemLen = getLength();
299        switch (tag)
300        {
301        case POLY_STATS_C_SECONDS:
302            seconds = parseUnsigned(elemLen);
303            break;
304        case POLY_STATS_C_MICROSECS:
305            useconds = parseUnsigned(elemLen);
306            break;
307        default: ptr += elemLen;
308        }
309    }
310    ULARGE_INTEGER li;
311    li.QuadPart = (ULONGLONG)seconds * 10000000 + (ULONGLONG)useconds * 10;
312    ft->dwHighDateTime = li.HighPart;
313    ft->dwLowDateTime = li.LowPart;
314}
315
316void ASN1Parse::parseAStatistic(int subTag, unsigned statLen)
317{
318    unsigned char *endOfStat = ptr+statLen;
319    unsigned tagId = 0;
320    while (ptr < endOfStat)
321    {
322        unsigned char tag = *ptr++;
323        unsigned elemLen = getLength();
324        switch (tag)
325        {
326        case POLY_STATS_C_IDENTIFIER:
327            // The identifier of the statistic
328            // We rely on the fact that the Id occurs before the value.
329            tagId = parseUnsigned(elemLen);
330            break;
331
332        case POLY_STATS_C_COUNTER_VALUE:
333            if (subTag = POLY_STATS_C_COUNTER_VALUE)
334            {
335                UINT32 cValue = parseUnsigned(elemLen);
336                // A counter value occurs in these statistics
337                switch (tagId)
338                {
339                case POLY_STATS_ID_THREADS:
340                    stats->psCounters[PSC_THREADS] = cValue; break;
341                case POLY_STATS_ID_THREADS_IN_ML:
342                    stats->psCounters[PSC_THREADS_IN_ML] = cValue; break;
343                case POLY_STATS_ID_THREADS_WAIT_IO:
344                    stats->psCounters[PSC_THREADS_WAIT_IO] = cValue; break;
345                case POLY_STATS_ID_THREADS_WAIT_MUTEX:
346                    stats->psCounters[PSC_THREADS_WAIT_MUTEX] = cValue; break;
347                case POLY_STATS_ID_THREADS_WAIT_CONDVAR:
348                    stats->psCounters[PSC_THREADS_WAIT_CONDVAR] = cValue; break;
349                case POLY_STATS_ID_THREADS_WAIT_SIGNAL:
350                    stats->psCounters[PSC_THREADS_WAIT_SIGNAL] = cValue; break;
351                case POLY_STATS_ID_GC_FULLGC:
352                    stats->psCounters[PSC_GC_FULLGC] = cValue; break;
353                case POLY_STATS_ID_GC_PARTIALGC:
354                    stats->psCounters[PSC_GC_PARTIALGC] = cValue; break;
355                case POLY_STATS_ID_GC_SHARING:
356                    stats->psCounters[PSC_GC_SHARING] = cValue; break;
357                case POLY_STATS_ID_USER0:
358                    stats->psUser[0] = cValue; break;
359                case POLY_STATS_ID_USER1:
360                    stats->psUser[1] = cValue; break;
361                case POLY_STATS_ID_USER2:
362                    stats->psUser[2] = cValue; break;
363                case POLY_STATS_ID_USER3:
364                    stats->psUser[3] = cValue; break;
365                case POLY_STATS_ID_USER4:
366                    stats->psUser[4] = cValue; break;
367                case POLY_STATS_ID_USER5:
368                    stats->psUser[5] = cValue; break;
369                case POLY_STATS_ID_USER6:
370                    stats->psUser[6] = cValue; break;
371                case POLY_STATS_ID_USER7:
372                    stats->psUser[7] = cValue; break;
373                // Anything else is an unknown tag; skip
374                }
375            }
376            else ptr += elemLen; // Skip it - not expected here
377            break;
378
379        case POLY_STATS_C_BYTE_COUNT:
380            if (subTag == POLY_STATS_C_BYTE_COUNT)
381            {
382                DWORD cValue = parseSize(elemLen);
383                switch (tagId)
384                {
385                case POLY_STATS_ID_TOTAL_HEAP:
386                    stats->psSizes[PSS_TOTAL_HEAP] = cValue; break;
387                case POLY_STATS_ID_AFTER_LAST_GC:
388                    stats->psSizes[PSS_AFTER_LAST_GC] = cValue; break;
389                case POLY_STATS_ID_AFTER_LAST_FULLGC:
390                    stats->psSizes[PSS_AFTER_LAST_FULLGC] = cValue; break;
391                case POLY_STATS_ID_ALLOCATION:
392                    stats->psSizes[PSS_ALLOCATION] = cValue; break;
393                case POLY_STATS_ID_ALLOCATION_FREE:
394                    stats->psSizes[PSS_ALLOCATION_FREE] = cValue; break;
395//                case POLY_STATS_ID_CODE_SPACE:
396//                    stats->psSizes[PSS_CODE_SPACE] = cValue; break;
397//                case POLY_STATS_ID_STACK_SPACE:
398//                    stats->psSizes[PSS_STACK_SPACE] = cValue; break;
399                }
400            }
401            else ptr += elemLen; // Skip it - not expected here
402            break;
403
404        case POLY_STATS_C_TIME:
405            if (subTag == POLY_STATS_C_TIME)
406            {
407                FILETIME ft = { 0, 0};
408                parseTime(&ft, elemLen);
409                switch (tagId)
410                {
411                case POLY_STATS_ID_NONGC_UTIME:
412                    stats->psTimers[PST_NONGC_UTIME] = ft; break;
413                case POLY_STATS_ID_NONGC_STIME:
414                    stats->psTimers[PST_NONGC_STIME] = ft; break;
415                case POLY_STATS_ID_GC_UTIME:
416                    stats->psTimers[PST_GC_UTIME] = ft; break;
417                case POLY_STATS_ID_GC_STIME:
418                    stats->psTimers[PST_GC_STIME] = ft; break;
419                case POLY_STATS_ID_NONGC_RTIME:
420                    stats->psTimers[PST_NONGC_RTIME] = ft; break;
421                case POLY_STATS_ID_GC_RTIME:
422                    stats->psTimers[PST_GC_RTIME] = ft; break;
423                }
424            }
425            else ptr += elemLen;
426            break;
427
428        default: ptr += elemLen; // Unknown - skip
429        }
430    }
431}
432
433// Pointer to table of processes with the Poly/ML run-time
434static PolyProcess **polyProcesses;
435static DWORD numProcesses;
436
437// Open: Find the current ML instances.
438DWORD APIENTRY OpenPolyPerfMon(LPWSTR lpInstanceNames)
439{
440    // Get the list of all process IDs.  Because we don't know
441    // how many there are we increase the buffer size until the
442    // size returned is less than the buffer size.
443    DWORD buffItems = 10, numItems;
444    DWORD *processIds = NULL;
445    while (true) {
446        processIds = (DWORD*)malloc(buffItems * sizeof(DWORD));
447        if (processIds == NULL)
448            return ERROR_NOT_ENOUGH_MEMORY;
449        DWORD bytesNeeded;
450        if (! EnumProcesses(processIds, buffItems * sizeof(DWORD), &bytesNeeded))
451            return GetLastError();
452        if (bytesNeeded < buffItems * sizeof(DWORD))
453        {
454            numItems = bytesNeeded / sizeof(DWORD);
455            break;
456        }
457        buffItems = buffItems * 2;
458        free(processIds);
459    }
460    // How many of these processes provide the Poly/ML shared memory?
461    // Make an array big enough for all processes to simplify allocation.
462    polyProcesses = (PolyProcess **)malloc(numItems * sizeof(PolyProcess*));
463    if (polyProcesses == NULL)
464    {
465        free(processIds);
466        free(polyProcesses);
467        return ERROR_NOT_ENOUGH_MEMORY;
468    }
469
470    for (DWORD dw = 0; dw < numItems; dw++)
471    {
472        // See if this is a Poly/ML process
473        PolyProcess *pProc = PolyProcess::CreateProcessEntry(processIds[dw]);
474        if (pProc != NULL) // We can use this
475            polyProcesses[numProcesses++] = pProc;
476    }
477
478    free(processIds);
479    return ERROR_SUCCESS;
480}
481
482// Delete the entries.
483DWORD APIENTRY ClosePolyPerfMon(void)
484{
485    if (polyProcesses != NULL)
486    {
487        for (DWORD dw = 0; dw < numProcesses; dw++)
488            delete(polyProcesses[dw]);
489        free(polyProcesses);
490    }
491    polyProcesses = NULL;
492    numProcesses = 0;
493
494    return ERROR_SUCCESS;
495}
496
497static LPVOID allocBuffSpace(LPVOID * &lppData, LPDWORD &lpcbTotalBytes, DWORD &dwBytesAvailable, DWORD size)
498{
499    if (dwBytesAvailable < size) return NULL;
500    LPVOID pResult = *lppData;
501    *lppData = (LPVOID)((char*)pResult + size);
502    memset(pResult, 0, size);
503    *lpcbTotalBytes += size;
504    dwBytesAvailable -= size;
505    return pResult;
506}
507
508// This is the entry that actually does the work.
509DWORD APIENTRY CollectPolyPerfMon(
510   /* IN     */LPWSTR lpRequest,
511   /* lpRequest is either "Global" (all counters) or a list of counter numbers to return.
512      These are the indexes into counter table in the Perflib\009 registry entry. */
513   /* IN OUT */LPVOID* lppData,
514   /* IN OUT */LPDWORD lpcbTotalBytes,
515   /* OUT    */LPDWORD lpNumObjectTypes)
516{
517    DWORD dwBytesAvailable = *lpcbTotalBytes;
518    LPVOID lpDataStart = *lppData;
519    *lpcbTotalBytes = 0;        // Bytes written
520    *lpNumObjectTypes = 0;      // Object types written
521    // For the moment we ignore the lpRequest argument and return all the counters.
522
523    // First find out where our strings are in the list.  This depends on
524    // the strings installed by other applications/services so will vary
525    // from machine to machine.  The installer will have added keys under
526    // our "service".  If these can't be read then there's nothing we can do.
527
528    HKEY hkPerform;
529    LONG err;
530
531    err = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
532            "SYSTEM\\CurrentControlSet\\Services\\PolyML\\Performance", 0,
533            KEY_READ, &hkPerform);
534    if (err != ERROR_SUCCESS)
535        return err;
536
537    DWORD dwType, dwSize, dwFirstCounter, dwFirstHelp;
538
539    dwSize = sizeof(dwFirstCounter);
540    err = RegQueryValueEx(hkPerform, "First Counter", 0, &dwType, (LPBYTE)&dwFirstCounter, &dwSize);
541    if (err != ERROR_SUCCESS)
542    {
543        RegCloseKey(hkPerform);
544        return err;
545    }
546
547    dwSize = sizeof(dwFirstHelp);
548    err = RegQueryValueEx(hkPerform, "First Help", 0, &dwType, (LPBYTE)&dwFirstHelp, &dwSize);
549    if (err != ERROR_SUCCESS)
550    {
551        RegCloseKey(hkPerform);
552        return err;
553    }
554    RegCloseKey(hkPerform);
555
556    // The actual strings are inserted by the installer.  See PolyML.wxs.
557    unsigned stringCount = 0;
558
559    // Object header.  Just one object.
560    PERF_OBJECT_TYPE *pObjectType =
561        (PERF_OBJECT_TYPE*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable, sizeof(PERF_OBJECT_TYPE));
562    if (pObjectType == NULL) return ERROR_MORE_DATA;
563    pObjectType->HeaderLength = sizeof(PERF_OBJECT_TYPE);
564    pObjectType->ObjectNameTitleIndex = dwFirstCounter + stringCount*2; // First string is the name of the object
565    pObjectType->ObjectHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
566    pObjectType->DetailLevel = PERF_DETAIL_NOVICE;
567    pObjectType->NumCounters = 0;
568    pObjectType->DefaultCounter = -1;
569    pObjectType->NumInstances = numProcesses;
570
571    // Counter block for each counter.
572    // First the numbers
573    PERF_COUNTER_DEFINITION *pCounters =
574        (PERF_COUNTER_DEFINITION*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable,
575            sizeof(PERF_COUNTER_DEFINITION) * N_PS_COUNTERS);
576    if (pCounters == NULL) return ERROR_MORE_DATA;
577    for (unsigned i = 0; i < N_PS_COUNTERS; i++)
578    {
579        pCounters[i].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
580        pCounters[i].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
581        pCounters[i].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
582        pCounters[i].DetailLevel = PERF_DETAIL_NOVICE;
583        pCounters[i].CounterType = PERF_COUNTER_RAWCOUNT;
584        pCounters[i].CounterSize = SIZEOF_COUNTER;
585        pCounters[i].CounterOffset = offsetof(statistics, psCounters)+i*SIZEOF_COUNTER;
586        pObjectType->NumCounters++;
587    }
588
589    // The sizes are dealt with specially.  We need to divide the values and express
590    // them as percentages.  Each displayed value is followed by a base value.
591    PERF_COUNTER_DEFINITION *pSizes =
592        (PERF_COUNTER_DEFINITION*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable,
593            sizeof(PERF_COUNTER_DEFINITION) * 6);
594    if (pSizes == NULL) return ERROR_MORE_DATA;
595    // First - Heap usage after last GC
596    pSizes[0].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
597    pSizes[0].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
598    pSizes[0].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
599    pSizes[0].DetailLevel = PERF_DETAIL_NOVICE;
600    pSizes[0].CounterType = PERF_RAW_FRACTION;
601    pSizes[0].CounterSize = SIZEOF_SIZE;
602    pSizes[0].CounterOffset =
603        offsetof(statistics, psSizes)+PSS_AFTER_LAST_GC*SIZEOF_SIZE;
604    pObjectType->NumCounters++;
605    pSizes[1].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
606    pSizes[1].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
607    pSizes[1].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
608    pSizes[1].DetailLevel = PERF_DETAIL_NOVICE;
609    pSizes[1].CounterType = PERF_RAW_BASE;
610    pSizes[1].CounterSize = SIZEOF_SIZE;
611    pSizes[1].CounterOffset =
612        offsetof(statistics, psSizes)+PSS_TOTAL_HEAP*SIZEOF_SIZE;
613    pObjectType->NumCounters++;
614    // Second - Heap usage after last full GC
615    pSizes[2].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
616    pSizes[2].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
617    pSizes[2].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
618    pSizes[2].DetailLevel = PERF_DETAIL_NOVICE;
619    pSizes[2].CounterType = PERF_RAW_FRACTION;
620    pSizes[2].CounterSize = SIZEOF_SIZE;
621    pSizes[2].CounterOffset =
622        offsetof(statistics, psSizes)+PSS_AFTER_LAST_FULLGC*SIZEOF_SIZE;
623    pObjectType->NumCounters++;
624    pSizes[3].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
625    pSizes[3].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
626    pSizes[3].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
627    pSizes[3].DetailLevel = PERF_DETAIL_NOVICE;
628    pSizes[3].CounterType = PERF_RAW_BASE;
629    pSizes[3].CounterSize = SIZEOF_SIZE;
630    pSizes[3].CounterOffset =
631        offsetof(statistics, psSizes)+PSS_TOTAL_HEAP*SIZEOF_SIZE;
632    pObjectType->NumCounters++;
633    // Third - Unreserved space in allocation area
634    pSizes[4].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
635    pSizes[4].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
636    pSizes[4].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
637    pSizes[4].DetailLevel = PERF_DETAIL_NOVICE;
638    pSizes[4].CounterType = PERF_RAW_FRACTION;
639    pSizes[4].CounterSize = SIZEOF_SIZE;
640    pSizes[4].CounterOffset =
641        offsetof(statistics, psSizes)+PSS_ALLOCATION_FREE*SIZEOF_SIZE;
642    pObjectType->NumCounters++;
643    pSizes[5].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
644    pSizes[5].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
645    pSizes[5].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
646    pSizes[5].DetailLevel = PERF_DETAIL_NOVICE;
647    pSizes[5].CounterType = PERF_RAW_BASE;
648    pSizes[5].CounterSize = SIZEOF_SIZE;
649    pSizes[5].CounterOffset =
650        offsetof(statistics, psSizes)+PSS_ALLOCATION*SIZEOF_SIZE;
651    pObjectType->NumCounters++;
652
653    // Then the times
654    PERF_COUNTER_DEFINITION *pTimes =
655        (PERF_COUNTER_DEFINITION*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable,
656            sizeof(PERF_COUNTER_DEFINITION) * N_PS_TIMES);
657    if (pTimes == NULL) return ERROR_MORE_DATA;
658    for (unsigned k = 0; k < N_PS_TIMES; k++)
659    {
660        pTimes[k].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
661        pTimes[k].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
662        pTimes[k].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
663        pTimes[k].DetailLevel = PERF_DETAIL_NOVICE;
664        pTimes[k].CounterType = PERF_100NSEC_TIMER;
665        pTimes[k].CounterSize = SIZEOF_TIME;
666        pTimes[k].CounterOffset = offsetof(statistics, psTimers)+k*SIZEOF_TIME;
667        pObjectType->NumCounters++;
668    }
669
670    // Finally the user counters
671    PERF_COUNTER_DEFINITION *pUsers =
672        (PERF_COUNTER_DEFINITION*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable,
673            sizeof(PERF_COUNTER_DEFINITION) * N_PS_USER);
674    if (pUsers == NULL) return ERROR_MORE_DATA;
675    for (unsigned l = 0; l < N_PS_USER; l++)
676    {
677        pUsers[l].ByteLength = sizeof(PERF_COUNTER_DEFINITION);
678        pUsers[l].CounterNameTitleIndex = dwFirstCounter + stringCount*2;
679        pUsers[l].CounterHelpTitleIndex = dwFirstHelp + (stringCount++)*2;
680        pUsers[l].DetailLevel = PERF_DETAIL_NOVICE;
681        pUsers[l].CounterType = PERF_COUNTER_RAWCOUNT;
682        pUsers[l].CounterSize = SIZEOF_USER;
683        pUsers[l].CounterOffset = offsetof(statistics, psUser)+l*SIZEOF_USER;
684        pObjectType->NumCounters++;
685    }
686
687    pObjectType->DefinitionLength = *lpcbTotalBytes; // End of definitions; start of instance data
688
689    // Instance data - One entry for each process.  Includes the instance name (i.e. the process)
690    // and the counter data.
691    for (DWORD dw = 0; dw < numProcesses; dw++)
692    {
693        PERF_INSTANCE_DEFINITION *pInst =
694            (PERF_INSTANCE_DEFINITION*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable, sizeof(PERF_INSTANCE_DEFINITION));
695        if (pInst == NULL) return ERROR_MORE_DATA;
696        PolyProcess *pProc = polyProcesses[dw];
697        pInst->UniqueID = PERF_NO_UNIQUE_ID; // Better to show the name
698        pInst->NameOffset = sizeof(PERF_INSTANCE_DEFINITION); // Name follows
699        DWORD len = (DWORD)wcslen(pProc->processName);
700        DWORD byteLength = (len+1)*sizeof(WCHAR); // Length including terminators
701        pInst->NameLength = byteLength;
702        byteLength = (byteLength + 7) / 8 * 8; // Must be rounded up to an eight-byte boundary.
703        pInst->ByteLength = byteLength + sizeof(PERF_INSTANCE_DEFINITION);
704        WCHAR *pName = (WCHAR*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable, byteLength);
705        wcscpy(pName, pProc->processName);
706
707        // Now the statistics including a PERF_COUNTER_BLOCK
708        DWORD statSize = (sizeof(statistics) + 7) / 8 * 8;
709        statistics *pStats  =
710            (statistics*)allocBuffSpace(lppData, lpcbTotalBytes, dwBytesAvailable, statSize);
711        if (pStats == NULL) return ERROR_MORE_DATA;
712
713        pStats->header.ByteLength = sizeof(PERF_COUNTER_BLOCK)+statSize;
714        ASN1Parse decode(pStats, pProc->sharedMem);
715        decode.asn1Decode();
716    }
717
718    pObjectType->TotalByteLength = *lpcbTotalBytes;
719    *lpNumObjectTypes = 1; // Single object
720    return ERROR_SUCCESS;
721}
722