src/jdk.management/windows/native/libmanagement_ext/OperatingSystemImpl.c
author phh
Sat, 30 Nov 2019 14:33:05 -0800
changeset 59330 5b96c12f909d
parent 47216 71c04702a3d5
permissions -rw-r--r--
8234541: C1 emits an empty message when it inlines successfully Summary: Use "inline" as the message when successfull Reviewed-by: thartmann, mdoerr Contributed-by: navy.xliu@gmail.com

/*
 * Copyright (c) 2003, 2015, 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)

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_getFreePhysicalMemorySize0
  (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_getTotalPhysicalMemorySize0
  (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 (ticks)
} UpdateQueryS, *UpdateQueryP;

// Min time between query updates (ticks)
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;

/* useful pdh fmt's */
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);

    fullCounterPathLen = strlen(objectName);
    fullCounterPathLen += strlen(counterName);

    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 + 1);

        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 + 1);

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

    fullCounterPath[fullCounterPathLen] = '\0';

    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).
 */
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 >= 0 &&
           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 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) vesions 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) {
    clock_t now = clock();

    /*
     * 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 += 2; // "%d"

    assert(pdhIDProcessCounterFmtLen < MAX_PATH);
    pdhIDProcessCounterFmt = malloc(pdhIDProcessCounterFmtLen + 1);
    if (!pdhIDProcessCounterFmt) {
        goto end;
    }

    /* "\Process(java#%d)\ID Process" */
    _snprintf(pdhIDProcessCounterFmt,
              pdhIDProcessCounterFmtLen,
              PROCESS_OBJECT_INSTANCE_COUNTER_FMT,
              pdhLocalizedProcessObject,
              pdhProcessImageName,
              "%d",
              pdhLocalizedIDProcessCounter);

    pdhIDProcessCounterFmt[pdhIDProcessCounterFmtLen] = '\0';

    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 (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_getSystemCpuLoad0
(JNIEnv *env, jobject dummy)
{
    return perfGetCPULoad(-1);
}

JNIEXPORT jdouble JNICALL
Java_com_sun_management_internal_OperatingSystemImpl_getProcessCpuLoad0
(JNIEnv *env, jobject dummy)
{
    return perfGetProcessCPULoad();
}