| /* | |
| * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include "jni.h" | |
| #include "jni_util.h" | |
| #include "jlong.h" | |
| #include "jvm.h" | |
| #include "management_ext.h" | |
| #include "com_sun_management_internal_OperatingSystemImpl.h" | |
| #include <psapi.h> | |
| #include <errno.h> | |
| #include <stdlib.h> | |
| #include <malloc.h> | |
| #pragma warning (push,0) | |
| #include <windows.h> | |
| #pragma warning (pop) | |
| #include <stdio.h> | |
| #include <time.h> | |
| #include <stdint.h> | |
| #include <assert.h> | |
| /* Disable warnings due to broken header files from Microsoft... */ | |
| #pragma warning(push, 3) | |
| #include <pdh.h> | |
| #include <pdhmsg.h> | |
| #include <process.h> | |
| #pragma warning(pop) | |
| #include <sysinfoapi.h> | |
| typedef unsigned __int32 juint; | |
| typedef unsigned __int64 julong; | |
| static void set_low(jlong* value, jint low) { | |
| *value &= (jlong)0xffffffff << 32; | |
| *value |= (jlong)(julong)(juint)low; | |
| } | |
| static void set_high(jlong* value, jint high) { | |
| *value &= (jlong)(julong)(juint)0xffffffff; | |
| *value |= (jlong)high << 32; | |
| } | |
| static jlong jlong_from(jint h, jint l) { | |
| jlong result = 0; // initialization to avoid warning | |
| set_high(&result, h); | |
| set_low(&result, l); | |
| return result; | |
| } | |
| static HANDLE main_process; | |
| static void perfInit(void); | |
| JNIEXPORT void JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_initialize0 | |
| (JNIEnv *env, jclass cls) | |
| { | |
| main_process = GetCurrentProcess(); | |
| perfInit(); | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getCommittedVirtualMemorySize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| PROCESS_MEMORY_COUNTERS pmc; | |
| if (GetProcessMemoryInfo(main_process, &pmc, sizeof(PROCESS_MEMORY_COUNTERS)) == 0) { | |
| return (jlong)-1L; | |
| } else { | |
| return (jlong) pmc.PagefileUsage; | |
| } | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getTotalSwapSpaceSize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| MEMORYSTATUSEX ms; | |
| ms.dwLength = sizeof(ms); | |
| GlobalMemoryStatusEx(&ms); | |
| return (jlong) ms.ullTotalPageFile; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getFreeSwapSpaceSize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| MEMORYSTATUSEX ms; | |
| ms.dwLength = sizeof(ms); | |
| GlobalMemoryStatusEx(&ms); | |
| return (jlong) ms.ullAvailPageFile; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getProcessCpuTime0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| FILETIME process_creation_time, process_exit_time, | |
| process_user_time, process_kernel_time; | |
| // Using static variables declared above | |
| // Units are 100-ns intervals. Convert to ns. | |
| GetProcessTimes(main_process, &process_creation_time, | |
| &process_exit_time, | |
| &process_kernel_time, &process_user_time); | |
| return (jlong_from(process_user_time.dwHighDateTime, | |
| process_user_time.dwLowDateTime) + | |
| jlong_from(process_kernel_time.dwHighDateTime, | |
| process_kernel_time.dwLowDateTime)) * 100; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getFreeMemorySize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| MEMORYSTATUSEX ms; | |
| ms.dwLength = sizeof(ms); | |
| GlobalMemoryStatusEx(&ms); | |
| return (jlong) ms.ullAvailPhys; | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getTotalMemorySize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| MEMORYSTATUSEX ms; | |
| ms.dwLength = sizeof(ms); | |
| GlobalMemoryStatusEx(&ms); | |
| return (jlong) ms.ullTotalPhys; | |
| } | |
| /* Performance Data Helper API (PDH) support */ | |
| typedef PDH_STATUS (WINAPI *PdhAddCounterFunc)( | |
| HQUERY hQuery, | |
| LPCSTR szFullCounterPath, | |
| DWORD dwUserData, | |
| HCOUNTER *phCounter | |
| ); | |
| typedef PDH_STATUS (WINAPI *PdhOpenQueryFunc)( | |
| LPCWSTR szDataSource, | |
| DWORD dwUserData, | |
| HQUERY *phQuery | |
| ); | |
| typedef PDH_STATUS (WINAPI *PdhCollectQueryDataFunc)( | |
| HQUERY hQuery | |
| ); | |
| typedef PDH_STATUS (WINAPI *PdhEnumObjectItemsFunc)( | |
| LPCTSTR szDataSource, | |
| LPCTSTR szMachineName, | |
| LPCTSTR szObjectName, | |
| LPTSTR mszCounterList, | |
| LPDWORD pcchCounterListLength, | |
| LPTSTR mszInstanceList, | |
| LPDWORD pcchInstanceListLength, | |
| DWORD dwDetailLevel, | |
| DWORD dwFlags | |
| ); | |
| typedef PDH_STATUS (WINAPI *PdhRemoveCounterFunc)( | |
| HCOUNTER hCounter | |
| ); | |
| typedef PDH_STATUS (WINAPI *PdhLookupPerfNameByIndexFunc)( | |
| LPCSTR szMachineName, | |
| DWORD dwNameIndex, | |
| LPSTR szNameBuffer, | |
| LPDWORD pcchNameBufferSize | |
| ); | |
| typedef DWORD (WINAPI *PdhCloseQueryFunc)( | |
| HQUERY hQuery | |
| ); | |
| typedef DWORD (WINAPI *PdhGetFormattedCounterValueFunc)( | |
| HCOUNTER hCounter, | |
| DWORD dwFormat, | |
| LPDWORD lpdwType, | |
| PPDH_FMT_COUNTERVALUE pValue | |
| ); | |
| static PdhAddCounterFunc PdhAddCounter_i; | |
| static PdhOpenQueryFunc PdhOpenQuery_i; | |
| static PdhCloseQueryFunc PdhCloseQuery_i; | |
| static PdhCollectQueryDataFunc PdhCollectQueryData_i; | |
| static PdhGetFormattedCounterValueFunc PdhGetFormattedCounterValue_i; | |
| static PdhEnumObjectItemsFunc PdhEnumObjectItems_i; | |
| static PdhRemoveCounterFunc PdhRemoveCounter_i; | |
| static PdhLookupPerfNameByIndexFunc PdhLookupPerfNameByIndex_i; | |
| /* | |
| * Struct for PDH queries. | |
| */ | |
| typedef struct { | |
| HQUERY query; | |
| uint64_t lastUpdate; // Last time query was updated (millis) | |
| } UpdateQueryS, *UpdateQueryP; | |
| // Min time between query updates (millis) | |
| static const int MIN_UPDATE_INTERVAL = 500; | |
| /* | |
| * Struct for a PDH query with multiple counters. | |
| */ | |
| typedef struct { | |
| UpdateQueryS query; | |
| HCOUNTER* counters; | |
| int noOfCounters; | |
| } MultipleCounterQueryS, *MultipleCounterQueryP; | |
| /* | |
| * Struct for a PDH query with a single counter. | |
| */ | |
| typedef struct { | |
| UpdateQueryS query; | |
| HCOUNTER counter; | |
| } SingleCounterQueryS, *SingleCounterQueryP; | |
| typedef struct { | |
| CRITICAL_SECTION cs; | |
| DWORD owningThread; | |
| DWORD recursionCount; | |
| } PdhCriticalSectionS, *PdhCriticalSectionP; | |
| static PdhCriticalSectionS initializationLock; | |
| static void InitializePdhCriticalSection(PdhCriticalSectionP criticalSection) { | |
| assert(criticalSection); | |
| InitializeCriticalSection(&criticalSection->cs); | |
| criticalSection->owningThread = 0; | |
| criticalSection->recursionCount = 0; | |
| } | |
| static void EnterPdhCriticalSection(PdhCriticalSectionP criticalSection) { | |
| assert(criticalSection); | |
| EnterCriticalSection(&criticalSection->cs); | |
| criticalSection->recursionCount++; | |
| if (!criticalSection->owningThread) { | |
| criticalSection->owningThread = GetCurrentThreadId(); | |
| } | |
| } | |
| static void LeavePdhCriticalSection(PdhCriticalSectionP criticalSection) { | |
| assert(criticalSection); | |
| assert(GetCurrentThreadId() == criticalSection->owningThread); | |
| assert(criticalSection->recursionCount >= 1); | |
| criticalSection->recursionCount--; | |
| if (!criticalSection->recursionCount) { | |
| criticalSection->owningThread = 0; | |
| } | |
| LeaveCriticalSection(&criticalSection->cs); | |
| } | |
| /* | |
| * INFO: Using PDH APIs Correctly in a Localized Language (Q287159) | |
| * http://support.microsoft.com/default.aspx?scid=kb;EN-US;q287159 | |
| * The index value for the base system counters and objects like processor, | |
| * process, thread, memory, and so forth are always the same irrespective | |
| * of the localized version of the operating system or service pack installed. | |
| * To find the correct index for an object or counter, inspect the registry key/value: | |
| * [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\009\Counter] | |
| */ | |
| static const DWORD PDH_PROCESSOR_IDX = 238; | |
| static const DWORD PDH_PROCESSOR_TIME_IDX = 6; | |
| static const DWORD PDH_PROCESS_IDX = 230; | |
| static const DWORD PDH_ID_PROCESS_IDX = 784; | |
| /* PDH format patterns, and lengths of their constant component. */ | |
| static const char* const OBJECT_COUNTER_FMT = "\\%s\\%s"; | |
| static const size_t OBJECT_COUNTER_FMT_LEN = 2; | |
| static const char* const OBJECT_WITH_INSTANCES_COUNTER_FMT = "\\%s(%s)\\%s"; | |
| static const size_t OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN = 4; | |
| static const char* const PROCESS_OBJECT_INSTANCE_COUNTER_FMT = "\\%s(%s#%s)\\%s"; | |
| static const size_t PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN = 5; | |
| static const char* pdhProcessImageName = NULL; /* "java" */ | |
| static char* pdhIDProcessCounterFmt = NULL; /* "\Process(java#%d)\ID Process" */ | |
| static int numberOfJavaProcessesAtInitialization = 0; | |
| /* | |
| * Currently used CPU queries/counters and variables | |
| */ | |
| static SingleCounterQueryP processTotalCPULoad = NULL; | |
| static MultipleCounterQueryP multiCounterCPULoad = NULL; | |
| static double cpuFactor = .0; | |
| static DWORD numCpus = 0; | |
| /* | |
| * Seems WinXP PDH returns PDH_MORE_DATA whenever we send in a NULL buffer. | |
| * Let's just ignore it, since we make sure we have enough buffer anyway. | |
| */ | |
| static int | |
| pdhFail(PDH_STATUS pdhStat) { | |
| return pdhStat != ERROR_SUCCESS && pdhStat != PDH_MORE_DATA; | |
| } | |
| static const char* | |
| allocateAndCopy(const char* const originalString) { | |
| size_t len; | |
| char* allocatedString; | |
| assert(originalString); | |
| len = strlen(originalString); | |
| allocatedString = malloc(len + 1); | |
| if (!allocatedString) { | |
| return NULL; | |
| } | |
| strncpy(allocatedString, originalString, len); | |
| allocatedString[len] = '\0'; | |
| return allocatedString; | |
| } | |
| /* | |
| * Allocates memory into the supplied pointer and | |
| * fills it with the localized PDH artifact description, if indexed correctly. | |
| * Caller owns the memory from the point of returning from this function. | |
| * | |
| * @param index the PDH counter index as specified in the registry | |
| * @param ppBuffer pointer to a char*. | |
| * @return 0 if successful, negative on failure. | |
| */ | |
| static int | |
| lookupNameByIndex(DWORD index, char** ppBuffer) { | |
| DWORD size; | |
| assert(ppBuffer); | |
| /* determine size needed */ | |
| if (PdhLookupPerfNameByIndex_i(NULL, index, NULL, &size) != PDH_MORE_DATA) { | |
| /* invalid index? */ | |
| return -1; | |
| } | |
| *ppBuffer = malloc((size_t)size); | |
| if (!*ppBuffer) { | |
| return -1; | |
| } | |
| if (PdhLookupPerfNameByIndex_i(NULL, index, *ppBuffer, &size) != ERROR_SUCCESS) { | |
| free(*ppBuffer); | |
| *ppBuffer = NULL; | |
| return -1; | |
| } | |
| /* windows vista does not null-terminate the string | |
| * (although the docs says it will) */ | |
| (*ppBuffer)[size - 1] = '\0'; | |
| return 0; | |
| } | |
| /* | |
| * Construct a fully qualified PDH path | |
| * | |
| * @param objectName a PDH Object string representation (required) | |
| * @param counterName a PDH Counter string representation (required) | |
| * @param imageName a process image name string, ex. "java" (opt) | |
| * @param instance an instance string, ex. "0", "1", ... (opt) | |
| * @return the fully qualified PDH path. | |
| * | |
| * Caller will own the returned malloc:ed string | |
| */ | |
| static const char* | |
| makeFullCounterPath(const char* const objectName, | |
| const char* const counterName, | |
| const char* const imageName, | |
| const char* const instance) { | |
| size_t fullCounterPathLen; | |
| char* fullCounterPath; | |
| assert(objectName); | |
| assert(counterName); | |
| // Always include space for null terminator: | |
| fullCounterPathLen = strlen(objectName) + strlen(counterName) + 1; | |
| if (imageName) { | |
| /* | |
| * For paths using the "Process" Object. | |
| * | |
| * Examples: | |
| * abstract: "\Process(imageName#instance)\Counter" | |
| * actual: "\Process(java#2)\ID Process" | |
| */ | |
| fullCounterPathLen += PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN; | |
| fullCounterPathLen += strlen(imageName); | |
| /* | |
| * imageName must be passed together with an associated | |
| * instance "number" ("0", "1", "2", ...). | |
| * This is required in order to create valid "Process" Object paths. | |
| * | |
| * Examples: "\Process(java#0)", \Process(java#1"), ... | |
| */ | |
| assert(instance); | |
| fullCounterPathLen += strlen(instance); | |
| fullCounterPath = malloc(fullCounterPathLen); | |
| if (!fullCounterPath) { | |
| return NULL; | |
| } | |
| snprintf(fullCounterPath, | |
| fullCounterPathLen, | |
| PROCESS_OBJECT_INSTANCE_COUNTER_FMT, | |
| objectName, | |
| imageName, | |
| instance, | |
| counterName); | |
| } else { | |
| if (instance) { | |
| /* | |
| * For paths where the Object has multiple instances. | |
| * | |
| * Examples: | |
| * abstract: "\Object(instance)\Counter" | |
| * actual: "\Processor(0)\% Privileged Time" | |
| */ | |
| fullCounterPathLen += strlen(instance); | |
| fullCounterPathLen += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; | |
| } else { | |
| /* | |
| * For "normal" paths. | |
| * | |
| * Examples: | |
| * abstract: "\Object\Counter" | |
| * actual: "\Memory\Available Mbytes" | |
| */ | |
| fullCounterPathLen += OBJECT_COUNTER_FMT_LEN; | |
| } | |
| fullCounterPath = malloc(fullCounterPathLen); | |
| if (!fullCounterPath) { | |
| return NULL; | |
| } | |
| if (instance) { | |
| snprintf(fullCounterPath, | |
| fullCounterPathLen, | |
| OBJECT_WITH_INSTANCES_COUNTER_FMT, | |
| objectName, | |
| instance, | |
| counterName); | |
| } else { | |
| snprintf(fullCounterPath, | |
| fullCounterPathLen, | |
| OBJECT_COUNTER_FMT, | |
| objectName, | |
| counterName); | |
| } | |
| } | |
| return fullCounterPath; | |
| } | |
| /* | |
| * Resolves an index for a PDH artifact to | |
| * a localized, malloc:ed string representation. | |
| * Caller will own the returned malloc:ed string. | |
| * | |
| * @param pdhArtifactIndex PDH index | |
| * @return malloc:ed string representation | |
| * of the requested pdh artifact (localized). | |
| * NULL on failure. | |
| */ | |
| static const char* | |
| getPdhLocalizedArtifact(DWORD pdhArtifactIndex) { | |
| char* pdhLocalizedArtifactString; | |
| if (lookupNameByIndex(pdhArtifactIndex, | |
| &pdhLocalizedArtifactString) != 0) { | |
| return NULL; | |
| } | |
| return pdhLocalizedArtifactString; | |
| } | |
| static void | |
| pdhCleanup(HQUERY* const query, HCOUNTER* const counter) { | |
| if (counter && *counter) { | |
| PdhRemoveCounter_i(*counter); | |
| *counter = NULL; | |
| } | |
| if (query && *query) { | |
| PdhCloseQuery_i(*query); | |
| *query = NULL; | |
| } | |
| } | |
| static void | |
| destroySingleCounter(SingleCounterQueryP counterQuery) { | |
| if (counterQuery) { | |
| pdhCleanup(&counterQuery->query.query, &counterQuery->counter); | |
| } | |
| } | |
| static void | |
| destroyMultiCounter(MultipleCounterQueryP multiCounterQuery) { | |
| int i; | |
| if (multiCounterQuery) { | |
| if (multiCounterQuery->counters) { | |
| for (i = 0; i < multiCounterQuery->noOfCounters; i++) { | |
| pdhCleanup(NULL, &multiCounterQuery->counters[i]); | |
| } | |
| free(multiCounterQuery->counters); | |
| multiCounterQuery->counters = NULL; | |
| } | |
| pdhCleanup(&multiCounterQuery->query.query, NULL); | |
| } | |
| } | |
| static int | |
| openQuery(HQUERY* const query) { | |
| assert(query); | |
| if (PdhOpenQuery_i(NULL, 0, query) != ERROR_SUCCESS) { | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| addCounter(HQUERY query, | |
| const char* const fullCounterPath, | |
| HCOUNTER* const counter) { | |
| assert(fullCounterPath); | |
| assert(counter); | |
| if (PdhAddCounter_i(query, | |
| fullCounterPath, | |
| 0, | |
| counter) != ERROR_SUCCESS) { | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Sets up the supplied SingleCounterQuery to listen for the specified counter. | |
| * | |
| * @param counterQuery the counter query to set up. | |
| * @param fullCounterPath the string specifying the full path to the counter. | |
| * @returns 0 if successful, negative on failure. | |
| */ | |
| static int | |
| initializeSingleCounterQuery(SingleCounterQueryP counterQuery, | |
| const char* const fullCounterPath) { | |
| assert(counterQuery); | |
| assert(fullCounterPath); | |
| if (openQuery(&counterQuery->query.query) == 0) { | |
| if (addCounter(counterQuery->query.query, | |
| fullCounterPath, | |
| &counterQuery->counter) == 0) { | |
| return 0; | |
| } | |
| } | |
| return -1; | |
| } | |
| /* | |
| * Sets up a SingleCounterQuery | |
| * | |
| * param counter the counter query to set up. | |
| * param localizedObject string representing the PDH object to query | |
| * param localizedCounter string representing the PDH counter to query | |
| * param processImageName if the counter query needs the process image name ("java") | |
| * param instance if the counter has instances, this is the instance ("\Processor(0)\") | |
| where 0 is the instance | |
| * param firstSampleOnInit for counters that need two queries to yield their values, | |
| the first query can be issued just after initialization | |
| * | |
| * @returns 0 if successful, negative on failure. | |
| */ | |
| static int | |
| initializeSingleCounter(SingleCounterQueryP const counter, | |
| const char* const localizedObject, | |
| const char* const localizedCounter, | |
| const char* const processImageName, | |
| const char* const instance, | |
| BOOL firstSampleOnInit) { | |
| int retValue = -1; | |
| const char* fullCounterPath = makeFullCounterPath(localizedObject, | |
| localizedCounter, | |
| processImageName, | |
| instance); | |
| if (fullCounterPath) { | |
| assert(counter); | |
| if (initializeSingleCounterQuery(counter, fullCounterPath) == 0) { | |
| /* | |
| * According to the MSDN documentation, rate counters must be read twice: | |
| * | |
| * "Obtaining the value of rate counters such as Page faults/sec requires that | |
| * PdhCollectQueryData be called twice, with a specific time interval between | |
| * the two calls, before calling PdhGetFormattedCounterValue. Call Sleep to | |
| * implement the waiting period between the two calls to PdhCollectQueryData." | |
| * | |
| * Take the first sample here already to allow for the next (first) "real" sample | |
| * to succeed. | |
| */ | |
| if (firstSampleOnInit) { | |
| PdhCollectQueryData_i(counter->query.query); | |
| } | |
| retValue = 0; | |
| } | |
| free((char*)fullCounterPath); | |
| } | |
| return retValue; | |
| } | |
| static void | |
| perfInit(void) { | |
| InitializePdhCriticalSection(&initializationLock); | |
| } | |
| static int | |
| getProcessID() { | |
| static int myPid = 0; | |
| if (0 == myPid) { | |
| myPid = _getpid(); | |
| } | |
| return myPid; | |
| } | |
| /* | |
| * Working against the Process object and it's related counters is inherently problematic | |
| * when using the PDH API: | |
| * | |
| * For PDH, a process is not primarily identified by it's process id, | |
| * but with a sequential number, for example \Process(java#0), \Process(java#1), .... | |
| * The really bad part is that this list is reset as soon as one process exits: | |
| * If \Process(java#1) exits, \Process(java#3) now becomes \Process(java#2) etc. | |
| * | |
| * The PDH query api requires a process identifier to be submitted when registering | |
| * a query, but as soon as the list resets, the query is invalidated (since the name | |
| * changed). | |
| * | |
| * Solution: | |
| * The #number identifier for a Process query can only decrease after process creation. | |
| * | |
| * Therefore we create an array of counter queries for all process object instances | |
| * up to and including ourselves: | |
| * | |
| * Ex. we come in as third process instance (java#2), we then create and register | |
| * queries for the following Process object instances: | |
| * java#0, java#1, java#2 | |
| * | |
| * currentQueryIndexForProcess() keeps track of the current "correct" query | |
| * (in order to keep this index valid when the list resets from underneath, | |
| * ensure to call getCurrentQueryIndexForProcess() before every query involving | |
| * Process object instance data). | |
| * | |
| * Returns -1 on failure. | |
| */ | |
| static int | |
| currentQueryIndexForProcess(void) { | |
| HQUERY tmpQuery = NULL; | |
| HCOUNTER handleCounter = NULL; | |
| int retValue = -1; | |
| assert(pdhProcessImageName); | |
| assert(pdhIDProcessCounterFmt); | |
| if (openQuery(&tmpQuery) == 0) { | |
| int index; | |
| /* iterate over all instance indexes and try to find our own pid */ | |
| for (index = 0; index < INT_MAX; ++index) { | |
| char fullIDProcessCounterPath[MAX_PATH]; | |
| PDH_FMT_COUNTERVALUE counterValue; | |
| PDH_STATUS res; | |
| snprintf(fullIDProcessCounterPath, | |
| MAX_PATH, | |
| pdhIDProcessCounterFmt, | |
| index); | |
| if (addCounter(tmpQuery, fullIDProcessCounterPath, &handleCounter) != 0) { | |
| break; | |
| } | |
| res = PdhCollectQueryData_i(tmpQuery); | |
| if (PDH_INVALID_HANDLE == res || PDH_NO_DATA == res) { | |
| break; | |
| } | |
| PdhGetFormattedCounterValue_i(handleCounter, | |
| PDH_FMT_LONG, | |
| NULL, | |
| &counterValue); | |
| /* | |
| * This check seems to be needed for Win2k SMP boxes, since | |
| * they for some reason don't return PDH_NO_DATA for non existing | |
| * counters. | |
| */ | |
| if (counterValue.CStatus != PDH_CSTATUS_VALID_DATA) { | |
| break; | |
| } | |
| if ((LONG)getProcessID() == counterValue.longValue) { | |
| retValue = index; | |
| break; | |
| } | |
| } | |
| } | |
| pdhCleanup(&tmpQuery, &handleCounter); | |
| return retValue; | |
| } | |
| /* | |
| * If successful, returns the #index corresponding to our PID | |
| * as resolved by the pdh query: | |
| * "\Process(java#index)\ID Process" (or localized equivalent) | |
| * | |
| * This function should be called before attempting to read | |
| * from any Process related counter(s), and the return value | |
| * is the index to be used for indexing an array of Process object query's: | |
| * | |
| * Example: | |
| * processTotalCPULoad[currentQueryIndex].query | |
| * | |
| * Returns -1 on failure. | |
| */ | |
| static int | |
| getCurrentQueryIndexForProcess() { | |
| int currentQueryIndex = currentQueryIndexForProcess(); | |
| assert(currentQueryIndex < numberOfJavaProcessesAtInitialization); | |
| return currentQueryIndex; | |
| } | |
| /* | |
| * Returns the PDH string identifying the current process image name. | |
| * Use this name as a qualifier when getting counters from the PDH Process Object | |
| * representing your process. | |
| * Example: | |
| * "\Process(java#0)\Virtual Bytes" - where "java" is the PDH process | |
| * image name. | |
| * | |
| * Please note that the process image name is not necessarily "java", | |
| * hence the use of GetModuleFileName() to detect the process image name. | |
| * | |
| * @return the process image name to be used when retrieving | |
| * PDH counters from the current process. The caller will | |
| own the returned malloc:ed string. NULL if failure. | |
| */ | |
| static const char* | |
| getPdhProcessImageName() { | |
| char moduleName[MAX_PATH]; | |
| char* processImageName; | |
| char* dotPos; | |
| // Find our module name and use it to extract the image name used by PDH | |
| DWORD getmfnReturn = GetModuleFileName(NULL, moduleName, sizeof(moduleName)); | |
| if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { | |
| return NULL; | |
| } | |
| if (getmfnReturn >= MAX_PATH || 0 == getmfnReturn) { | |
| return NULL; | |
| } | |
| processImageName = strrchr(moduleName, '\\'); //drop path | |
| processImageName++; //skip slash | |
| dotPos = strrchr(processImageName, '.'); //drop .exe | |
| dotPos[0] = '\0'; | |
| return allocateAndCopy(processImageName); | |
| } | |
| /* | |
| * Sets up the supplied MultipleCounterQuery to check on the processors via PDH CPU counters. | |
| * TODO: Refactor and prettify as with the SingleCounter queries | |
| * if more MultipleCounterQueries are discovered/needed. | |
| * | |
| * @param multiCounterCPULoad a pointer to a MultipleCounterQueryS, will be filled in with | |
| * the necessary info to check the PDH processor counters. | |
| * @return 0 if successful, negative on failure. | |
| */ | |
| static int | |
| initializeMultipleCounterForCPUs(MultipleCounterQueryP multiCounterCPULoad) { | |
| DWORD cSize = 0; | |
| DWORD iSize = 0; | |
| DWORD pCount; | |
| DWORD index; | |
| char* processor = NULL; //'Processor' == PDH_PROCESSOR_IDX | |
| char* time = NULL; //'Time' == PDH_PROCESSOR_TIME_IDX | |
| char* instances = NULL; | |
| char* tmp; | |
| int retValue = -1; | |
| PDH_STATUS pdhStat; | |
| if (lookupNameByIndex(PDH_PROCESSOR_IDX, &processor) != 0) { | |
| goto end; | |
| } | |
| if (lookupNameByIndex(PDH_PROCESSOR_TIME_IDX, &time) != 0) { | |
| goto end; | |
| } | |
| //ok, now we have enough to enumerate all processors. | |
| pdhStat = PdhEnumObjectItems_i( | |
| NULL, // reserved | |
| NULL, // local machine | |
| processor, // object to enumerate | |
| NULL, // pass in NULL buffers | |
| &cSize, // and 0 length to get | |
| NULL, // required size | |
| &iSize, // of the buffers in chars | |
| PERF_DETAIL_WIZARD, // counter detail level | |
| 0); | |
| if (pdhFail(pdhStat)) { | |
| goto end; | |
| } | |
| instances = calloc(iSize, 1); | |
| if (!instances) { | |
| goto end; | |
| } | |
| cSize = 0; | |
| pdhStat = PdhEnumObjectItems_i( | |
| NULL, // reserved | |
| NULL, // local machine | |
| processor, // object to enumerate | |
| NULL, // pass in NULL buffers | |
| &cSize, | |
| instances, // now allocated to be filled in | |
| &iSize, // and size is known | |
| PERF_DETAIL_WIZARD, // counter detail level | |
| 0); | |
| if (pdhFail(pdhStat)) { | |
| goto end; | |
| } | |
| // enumerate the Processor instances ("\Processor(0)", "\Processor(1)", ..., "\Processor(_Total)") | |
| for (pCount = 0, tmp = instances; *tmp != '\0'; tmp = &tmp[strlen(tmp)+1], pCount++); | |
| assert(pCount == numCpus+1); | |
| //ok, we now have the number of Processor instances - allocate an HCOUNTER for each | |
| multiCounterCPULoad->counters = (HCOUNTER*)malloc(pCount * sizeof(HCOUNTER)); | |
| if (!multiCounterCPULoad->counters) { | |
| goto end; | |
| } | |
| multiCounterCPULoad->noOfCounters = pCount; | |
| if (openQuery(&multiCounterCPULoad->query.query) != 0) { | |
| goto end; | |
| } | |
| // fetch instance and register its corresponding HCOUNTER with the query | |
| for (index = 0, tmp = instances; *tmp != '\0'; tmp = &tmp[strlen(tmp)+1], ++index) { | |
| const char* const fullCounterPath = makeFullCounterPath(processor, time, NULL, tmp); | |
| if (!fullCounterPath) { | |
| goto end; | |
| } | |
| retValue = addCounter(multiCounterCPULoad->query.query, | |
| fullCounterPath, | |
| &multiCounterCPULoad->counters[index]); | |
| free((char*)fullCounterPath); | |
| if (retValue != 0) { | |
| goto end; | |
| } | |
| } | |
| // Query once to initialize the counters which require at least two samples | |
| // (like the % CPU usage) to calculate correctly. | |
| PdhCollectQueryData_i(multiCounterCPULoad->query.query); | |
| end: | |
| if (processor) { | |
| free(processor); | |
| } | |
| if (time) { | |
| free(time); | |
| } | |
| if (instances) { | |
| free(instances); | |
| } | |
| return retValue; | |
| } | |
| /* | |
| * Dynamically sets up function pointers to the PDH library. | |
| * | |
| * @param h HMODULE for the PDH library | |
| * @return 0 on success, negative on failure. | |
| */ | |
| static int | |
| bindPdhFunctionPointers(HMODULE h) { | |
| assert(h); | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| /* The 'A' at the end means the ANSI (not the UNICODE) versions of the methods */ | |
| PdhAddCounter_i = (PdhAddCounterFunc)GetProcAddress(h, "PdhAddCounterA"); | |
| PdhOpenQuery_i = (PdhOpenQueryFunc)GetProcAddress(h, "PdhOpenQueryA"); | |
| PdhCloseQuery_i = (PdhCloseQueryFunc)GetProcAddress(h, "PdhCloseQuery"); | |
| PdhCollectQueryData_i = (PdhCollectQueryDataFunc)GetProcAddress(h, "PdhCollectQueryData"); | |
| PdhGetFormattedCounterValue_i = (PdhGetFormattedCounterValueFunc)GetProcAddress(h, "PdhGetFormattedCounterValue"); | |
| PdhEnumObjectItems_i = (PdhEnumObjectItemsFunc)GetProcAddress(h, "PdhEnumObjectItemsA"); | |
| PdhRemoveCounter_i = (PdhRemoveCounterFunc)GetProcAddress(h, "PdhRemoveCounter"); | |
| PdhLookupPerfNameByIndex_i = (PdhLookupPerfNameByIndexFunc)GetProcAddress(h, "PdhLookupPerfNameByIndexA"); | |
| if (!PdhAddCounter_i || !PdhOpenQuery_i || | |
| !PdhCloseQuery_i || !PdhCollectQueryData_i || | |
| !PdhGetFormattedCounterValue_i || !PdhEnumObjectItems_i || | |
| !PdhRemoveCounter_i || !PdhLookupPerfNameByIndex_i) | |
| { | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Returns the counter value as a double for the specified query. | |
| * Will collect the query data and update the counter values as necessary. | |
| * | |
| * @param query the query to update (if needed). | |
| * @param c the counter to read. | |
| * @param value where to store the formatted value. | |
| * @param format the format to use (i.e. PDH_FMT_DOUBLE, PDH_FMT_LONG etc) | |
| * @return 0 if no error | |
| * -1 if PdhCollectQueryData fails | |
| * -2 if PdhGetFormattedCounterValue fails | |
| */ | |
| static int | |
| getPerformanceData(UpdateQueryP query, HCOUNTER c, PDH_FMT_COUNTERVALUE* value, DWORD format) { | |
| uint64_t now = GetTickCount64(); | |
| /* | |
| * Need to limit how often we update the query | |
| * to minimize the Heisenberg effect. | |
| * (PDH behaves erratically if the counters are | |
| * queried too often, especially counters that | |
| * store and use values from two consecutive updates, | |
| * like cpu load.) | |
| */ | |
| if (now - query->lastUpdate > MIN_UPDATE_INTERVAL) { | |
| if (PdhCollectQueryData_i(query->query) != ERROR_SUCCESS) { | |
| return -1; | |
| } | |
| query->lastUpdate = now; | |
| } | |
| if (PdhGetFormattedCounterValue_i(c, format, NULL, value) != ERROR_SUCCESS) { | |
| return -2; | |
| } | |
| return 0; | |
| } | |
| static int | |
| allocateAndInitializePdhConstants() { | |
| const char* pdhLocalizedProcessObject = NULL; | |
| const char* pdhLocalizedIDProcessCounter = NULL; | |
| size_t pdhIDProcessCounterFmtLen; | |
| int currentQueryIndex; | |
| int retValue = -1; | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| assert(!pdhProcessImageName); | |
| pdhProcessImageName = getPdhProcessImageName(); | |
| if (!pdhProcessImageName) { | |
| goto end; | |
| } | |
| pdhLocalizedProcessObject = getPdhLocalizedArtifact(PDH_PROCESS_IDX); | |
| if (!pdhLocalizedProcessObject) { | |
| goto end; | |
| } | |
| pdhLocalizedIDProcessCounter = getPdhLocalizedArtifact(PDH_ID_PROCESS_IDX); | |
| if (!pdhLocalizedIDProcessCounter) { | |
| goto end; | |
| } | |
| assert(!pdhIDProcessCounterFmt); | |
| pdhIDProcessCounterFmtLen = strlen(pdhProcessImageName); | |
| pdhIDProcessCounterFmtLen += strlen(pdhLocalizedProcessObject); | |
| pdhIDProcessCounterFmtLen += strlen(pdhLocalizedIDProcessCounter); | |
| pdhIDProcessCounterFmtLen += PROCESS_OBJECT_INSTANCE_COUNTER_FMT_LEN; | |
| pdhIDProcessCounterFmtLen += 3; // "%d" and '\0' | |
| assert(pdhIDProcessCounterFmtLen < MAX_PATH); | |
| pdhIDProcessCounterFmt = malloc(pdhIDProcessCounterFmtLen); | |
| if (!pdhIDProcessCounterFmt) { | |
| goto end; | |
| } | |
| /* "\Process(java#%d)\ID Process" */ | |
| snprintf(pdhIDProcessCounterFmt, | |
| pdhIDProcessCounterFmtLen, | |
| PROCESS_OBJECT_INSTANCE_COUNTER_FMT, | |
| pdhLocalizedProcessObject, | |
| pdhProcessImageName, | |
| "%d", | |
| pdhLocalizedIDProcessCounter); | |
| assert(0 == numberOfJavaProcessesAtInitialization); | |
| currentQueryIndex = currentQueryIndexForProcess(); | |
| if (-1 == currentQueryIndex) { | |
| goto end; | |
| } | |
| numberOfJavaProcessesAtInitialization = currentQueryIndex + 1; | |
| assert(numberOfJavaProcessesAtInitialization >= 1); | |
| retValue = 0; | |
| end: | |
| if (pdhLocalizedProcessObject) { | |
| free((char*)pdhLocalizedProcessObject); | |
| } | |
| if (pdhLocalizedIDProcessCounter) { | |
| free((char*)pdhLocalizedIDProcessCounter); | |
| } | |
| return retValue; | |
| } | |
| static void | |
| deallocatePdhConstants() { | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| if (pdhProcessImageName) { | |
| free((char*)pdhProcessImageName); | |
| pdhProcessImageName = NULL; | |
| } | |
| if (pdhIDProcessCounterFmt) { | |
| free(pdhIDProcessCounterFmt); | |
| pdhIDProcessCounterFmt = NULL; | |
| } | |
| numberOfJavaProcessesAtInitialization = 0; | |
| } | |
| static int | |
| initializeCPUCounters() { | |
| SYSTEM_INFO si; | |
| char* localizedProcessObject; | |
| char* localizedProcessorTimeCounter; | |
| int i; | |
| int retValue = -1; | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| assert(0 == numCpus); | |
| GetSystemInfo(&si); | |
| numCpus = si.dwNumberOfProcessors; | |
| assert(numCpus >= 1); | |
| /* Initialize the denominator for the jvm load calculations */ | |
| assert(.0 == cpuFactor); | |
| cpuFactor = numCpus * 100; | |
| if (lookupNameByIndex(PDH_PROCESS_IDX, | |
| &localizedProcessObject) == 0) { | |
| if (lookupNameByIndex(PDH_PROCESSOR_TIME_IDX, | |
| &localizedProcessorTimeCounter) == 0) { | |
| assert(processTotalCPULoad); | |
| assert(pdhProcessImageName); | |
| for (i = 0; i < numberOfJavaProcessesAtInitialization; ++i) { | |
| char instanceIndexBuffer[32]; | |
| retValue = initializeSingleCounter(&processTotalCPULoad[i], | |
| localizedProcessObject, | |
| localizedProcessorTimeCounter, | |
| pdhProcessImageName, | |
| itoa(i, instanceIndexBuffer, 10), | |
| TRUE); | |
| if (retValue != 0) { | |
| break; | |
| } | |
| } | |
| free(localizedProcessorTimeCounter); | |
| } | |
| free(localizedProcessObject); | |
| } | |
| if (retValue != 0) { | |
| return -1; | |
| } | |
| assert(multiCounterCPULoad); | |
| return initializeMultipleCounterForCPUs(multiCounterCPULoad); | |
| } | |
| static void | |
| deallocateCPUCounters() { | |
| int i; | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| if (processTotalCPULoad) { | |
| for (i = 0; i < numberOfJavaProcessesAtInitialization; ++i) { | |
| destroySingleCounter(&processTotalCPULoad[i]); | |
| } | |
| free(processTotalCPULoad); | |
| processTotalCPULoad = NULL; | |
| } | |
| if (multiCounterCPULoad) { | |
| destroyMultiCounter(multiCounterCPULoad); | |
| free(multiCounterCPULoad); | |
| multiCounterCPULoad = NULL; | |
| } | |
| cpuFactor = .0; | |
| numCpus = 0; | |
| } | |
| static void | |
| pdhInitErrorHandler(HMODULE h) { | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| deallocatePdhConstants(); | |
| if (h) { | |
| FreeLibrary(h); | |
| } | |
| } | |
| /* | |
| * Helper to initialize the PDH library, function pointers and constants. | |
| * | |
| * @return 0 if successful, negative on failure. | |
| */ | |
| static int | |
| pdhInit() { | |
| static BOOL initialized = FALSE; | |
| int retValue; | |
| if (initialized) { | |
| return 0; | |
| } | |
| retValue = 0; | |
| EnterPdhCriticalSection(&initializationLock); { | |
| if (!initialized) { | |
| HMODULE h = NULL; | |
| if ((h = LoadLibrary("pdh.dll")) == NULL) { | |
| retValue = -1; | |
| } else if (bindPdhFunctionPointers(h) < 0) { | |
| retValue = -1; | |
| } else if (allocateAndInitializePdhConstants() < 0) { | |
| retValue = -1; | |
| } | |
| if (0 == retValue) { | |
| initialized = TRUE; | |
| } else { | |
| pdhInitErrorHandler(h); | |
| } | |
| } | |
| } LeavePdhCriticalSection(&initializationLock); | |
| return retValue; | |
| } | |
| static int | |
| allocateCPUCounters() { | |
| assert(GetCurrentThreadId() == initializationLock.owningThread); | |
| assert(numberOfJavaProcessesAtInitialization >= 1); | |
| assert(!processTotalCPULoad); | |
| assert(!multiCounterCPULoad); | |
| /* | |
| * Create an array of Process object queries, for each instance | |
| * up to and including our own (java#0, java#1, java#2, ...). | |
| */ | |
| processTotalCPULoad = calloc(numberOfJavaProcessesAtInitialization, | |
| sizeof(SingleCounterQueryS)); | |
| if (!processTotalCPULoad) { | |
| return -1; | |
| } | |
| multiCounterCPULoad = calloc(1, sizeof(MultipleCounterQueryS)); | |
| if (!multiCounterCPULoad) { | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| initializePdhCPUCounters() { | |
| static BOOL initialized = FALSE; | |
| int retValue; | |
| if (initialized) { | |
| return 0; | |
| } | |
| retValue = 0; | |
| EnterPdhCriticalSection(&initializationLock); { | |
| if (!initialized) { | |
| if (pdhInit() < 0) { | |
| retValue = -1; | |
| } else if (allocateCPUCounters() < 0) { | |
| retValue = -1; | |
| } else if (initializeCPUCounters() < 0) { | |
| retValue = -1; | |
| } | |
| if (0 == retValue) { | |
| initialized = TRUE; | |
| } else { | |
| deallocateCPUCounters(); | |
| } | |
| } | |
| } LeavePdhCriticalSection(&initializationLock); | |
| return retValue; | |
| } | |
| static int | |
| perfCPUInit() { | |
| return initializePdhCPUCounters(); | |
| } | |
| static double | |
| perfGetProcessCPULoad() { | |
| PDH_FMT_COUNTERVALUE cv; | |
| int currentQueryIndex; | |
| if (perfCPUInit() < 0) { | |
| // warn? | |
| return -1.0; | |
| } | |
| currentQueryIndex = getCurrentQueryIndexForProcess(); | |
| if (currentQueryIndex >= 0 && getPerformanceData(&processTotalCPULoad[currentQueryIndex].query, | |
| processTotalCPULoad[currentQueryIndex].counter, | |
| &cv, | |
| PDH_FMT_DOUBLE | PDH_FMT_NOCAP100) == 0) { | |
| double d = cv.doubleValue / cpuFactor; | |
| d = min(1, d); | |
| d = max(0, d); | |
| return d; | |
| } | |
| return -1.0; | |
| } | |
| static double | |
| perfGetCPULoad(int which) { | |
| PDH_FMT_COUNTERVALUE cv; | |
| HCOUNTER c; | |
| if (perfCPUInit() < 0) { | |
| // warn? | |
| return -1.0; | |
| } | |
| if (-1 == which) { | |
| c = multiCounterCPULoad->counters[multiCounterCPULoad->noOfCounters - 1]; | |
| } else { | |
| if (which < multiCounterCPULoad->noOfCounters) { | |
| c = multiCounterCPULoad->counters[which]; | |
| } else { | |
| return -1.0; | |
| } | |
| } | |
| if (getPerformanceData(&multiCounterCPULoad->query, c, &cv, PDH_FMT_DOUBLE ) == 0) { | |
| return cv.doubleValue / 100; | |
| } | |
| return -1.0; | |
| } | |
| JNIEXPORT jdouble JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getCpuLoad0 | |
| (JNIEnv *env, jobject dummy) | |
| { | |
| return perfGetCPULoad(-1); | |
| } | |
| JNIEXPORT jdouble JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getProcessCpuLoad0 | |
| (JNIEnv *env, jobject dummy) | |
| { | |
| return perfGetProcessCPULoad(); | |
| } |