| /* |
| * Copyright (c) 2012, 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. |
| * |
| * 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 "iphlp_interface.hpp" |
| #include "jvm_io.h" |
| #include "logging/log.hpp" |
| #include "memory/allocation.inline.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "pdh_interface.hpp" |
| #include "runtime/os_perf.hpp" |
| #include "runtime/os.hpp" |
| #include "runtime/semaphore.inline.hpp" |
| #include "runtime/vm_version.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| #include "utilities/macros.hpp" |
| |
| #include <math.h> |
| #include <psapi.h> |
| #include <TlHelp32.h> |
| |
| /* |
| * Windows provides a vast plethora of performance objects and counters, |
| * consumption of which is assisted using the Performance Data Helper (PDH) interface. |
| * We import a selected few api entry points from PDH, see pdh_interface.hpp. |
| * |
| * The code located in this file is to a large extent an abstraction over much of the |
| * plumbing needed to start consuming an object and/or counter of choice. |
| * |
| * |
| * How to use: |
| * 1. Create a query |
| * 2. Add counters to the query |
| * 3. Collect the performance data using the query |
| * 4. Read the performance data from counters associated with the query |
| * 5. Destroy query (counter destruction implied) |
| * |
| * |
| * Every PDH artifact, like processor, process, thread, memory, and so forth are |
| * identified with an index that is always the same irrespective |
| * of the localized version of the operating system or service pack installed. |
| * INFO: Using PDH APIs Correctly in a Localized Language (Q287159) |
| * http://support.microsoft.com/default.aspx?scid=kb;EN-US;q287159 |
| * |
| * 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] |
| * |
| * some common PDH indexes |
| */ |
| static const DWORD PDH_PROCESSOR_IDX = 238; |
| static const DWORD PDH_PROCESSOR_TIME_IDX = 6; |
| static const DWORD PDH_PRIV_PROCESSOR_TIME_IDX = 144; |
| static const DWORD PDH_PROCESS_IDX = 230; |
| static const DWORD PDH_ID_PROCESS_IDX = 784; |
| static const DWORD PDH_CONTEXT_SWITCH_RATE_IDX = 146; |
| static const DWORD PDH_SYSTEM_IDX = 2; |
| |
| /* useful pdh fmt's for the general form: \object(instance#index)\counter */ |
| 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_WITH_INSTANCES_COUNTER_FMT = "\\%s(%s#%s)\\%s"; |
| static const size_t PROCESS_OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN = 5; |
| static const char* const PROCESS_OBJECT_WITH_INSTANCES_WILDCARD_FMT = "\\%s(%s*)\\%s"; |
| static const size_t PROCESS_OBJECT_WITH_INSTANCES_WILDCARD_FMT_LEN = 5; |
| |
| /* pdh string constants built up from fmts on initialization */ |
| static const char* process_image_name = nullptr; // e.g. "java" but could have another image name |
| static char* pdh_process_instance_IDProcess_counter_fmt = nullptr; // "\Process(java#%d)\ID Process" */ |
| static char* pdh_process_instance_wildcard_IDProcess_counter = nullptr; // "\Process(java*)\ID Process" */ |
| |
| /* |
| * Structs for PDH queries. |
| */ |
| typedef struct { |
| HQUERY pdh_query_handle; |
| s8 lastUpdate; // Last time query was updated. |
| } UpdateQueryS, *UpdateQueryP; |
| |
| |
| typedef struct { |
| UpdateQueryS query; |
| HCOUNTER counter; |
| bool initialized; |
| } CounterQueryS, *CounterQueryP; |
| |
| typedef struct { |
| UpdateQueryS query; |
| HCOUNTER* counters; |
| int noOfCounters; |
| bool initialized; |
| } MultiCounterQueryS, *MultiCounterQueryP; |
| |
| typedef struct { |
| MultiCounterQueryP queries; |
| int size; |
| bool initialized; |
| } MultiCounterQuerySetS, *MultiCounterQuerySetP; |
| |
| typedef struct { |
| MultiCounterQuerySetS set; |
| int process_idx; |
| } ProcessQueryS, *ProcessQueryP; |
| |
| static int open_query(HQUERY* pdh_query_handle) { |
| return PdhDll::PdhOpenQuery(nullptr, 0, pdh_query_handle) != ERROR_SUCCESS ? OS_ERR : OS_OK; |
| } |
| |
| static int open_query(UpdateQueryP query) { |
| return open_query(&query->pdh_query_handle); |
| } |
| |
| template <typename QueryP> |
| static int open_query(QueryP query) { |
| return open_query(&query->query); |
| } |
| |
| static void close_query(HQUERY* const pdh_query_handle, HCOUNTER* const counter) { |
| if (counter != nullptr && *counter != nullptr) { |
| PdhDll::PdhRemoveCounter(*counter); |
| *counter = nullptr; |
| } |
| if (pdh_query_handle != nullptr && *pdh_query_handle != nullptr) { |
| PdhDll::PdhCloseQuery(*pdh_query_handle); |
| *pdh_query_handle = nullptr; |
| } |
| } |
| |
| static void close_query(MultiCounterQueryP query) { |
| for (int i = 0; i < query->noOfCounters; ++i) { |
| close_query(nullptr, &query->counters[i]); |
| } |
| close_query(&query->query.pdh_query_handle, nullptr); |
| query->initialized = false; |
| } |
| |
| static CounterQueryP create_counter_query() { |
| CounterQueryP const query = NEW_C_HEAP_OBJ(CounterQueryS, mtInternal); |
| memset(query, 0, sizeof(CounterQueryS)); |
| return query; |
| } |
| |
| static MultiCounterQueryP create_multi_counter_query() { |
| MultiCounterQueryP const query = NEW_C_HEAP_ARRAY(MultiCounterQueryS, 1, mtInternal); |
| memset(query, 0, sizeof(MultiCounterQueryS)); |
| return query; |
| } |
| |
| static void destroy(CounterQueryP query) { |
| assert(query != nullptr, "invariant"); |
| close_query(&query->query.pdh_query_handle, &query->counter); |
| FREE_C_HEAP_OBJ(query); |
| } |
| |
| static void destroy(MultiCounterQueryP query) { |
| if (query != nullptr) { |
| for (int i = 0; i < query->noOfCounters; ++i) { |
| close_query(nullptr, &query->counters[i]); |
| } |
| FREE_C_HEAP_ARRAY(char, query->counters); |
| close_query(&query->query.pdh_query_handle, nullptr); |
| FREE_C_HEAP_ARRAY(MultiCounterQueryS, query); |
| } |
| } |
| |
| static void destroy_query_set(MultiCounterQuerySetP query_set) { |
| for (int i = 0; i < query_set->size; i++) { |
| for (int j = 0; j < query_set->queries[i].noOfCounters; ++j) { |
| close_query(nullptr, &query_set->queries[i].counters[j]); |
| } |
| FREE_C_HEAP_ARRAY(char, query_set->queries[i].counters); |
| close_query(&query_set->queries[i].query.pdh_query_handle, nullptr); |
| } |
| FREE_C_HEAP_ARRAY(MultiCounterQueryS, query_set->queries); |
| } |
| |
| static void destroy(MultiCounterQuerySetP query) { |
| destroy_query_set(query); |
| FREE_C_HEAP_ARRAY(MultiCounterQuerySetS, query); |
| } |
| |
| static void destroy(ProcessQueryP query) { |
| destroy_query_set(&query->set); |
| FREE_C_HEAP_OBJ(query); |
| } |
| |
| static void allocate_counters(MultiCounterQueryP query, size_t nofCounters) { |
| assert(query != nullptr, "invariant"); |
| assert(!query->initialized, "invariant"); |
| assert(0 == query->noOfCounters, "invariant"); |
| assert(query->counters == nullptr, "invariant"); |
| query->counters = NEW_C_HEAP_ARRAY(HCOUNTER, nofCounters, mtInternal); |
| memset(query->counters, 0, nofCounters * sizeof(HCOUNTER)); |
| query->noOfCounters = (int)nofCounters; |
| } |
| |
| static void allocate_counters(MultiCounterQuerySetP query, size_t nofCounters) { |
| assert(query != nullptr, "invariant"); |
| assert(!query->initialized, "invariant"); |
| for (int i = 0; i < query->size; ++i) { |
| allocate_counters(&query->queries[i], nofCounters); |
| } |
| } |
| |
| static void allocate_counters(ProcessQueryP query, size_t nofCounters) { |
| assert(query != nullptr, "invariant"); |
| allocate_counters(&query->set, nofCounters); |
| } |
| |
| static void deallocate_counters(MultiCounterQueryP query) { |
| FREE_C_HEAP_ARRAY(char, query->counters); |
| query->counters = nullptr; |
| query->noOfCounters = 0; |
| } |
| |
| static OSReturn add_counter(UpdateQueryP query, HCOUNTER* counter, const char* counter_path, bool first_sample_on_init) { |
| assert(query != nullptr, "invariant"); |
| assert(counter != nullptr, "invariant"); |
| assert(counter_path != nullptr, "invariant"); |
| if (query->pdh_query_handle == nullptr) { |
| if (open_query(query) != OS_OK) { |
| return OS_ERR; |
| } |
| } |
| assert(query->pdh_query_handle != nullptr, "invariant"); |
| PDH_STATUS status = PdhDll::PdhAddCounter(query->pdh_query_handle, counter_path, 0, counter); |
| if (PDH_CSTATUS_NO_OBJECT == status || PDH_CSTATUS_NO_COUNTER == status) { |
| return OS_ERR; |
| } |
| /* |
| * 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 "real" sample |
| * to succeed. |
| */ |
| if (first_sample_on_init && PdhDll::PdhCollectQueryData(query->pdh_query_handle) != ERROR_SUCCESS) { |
| return OS_ERR; |
| } |
| return OS_OK; |
| } |
| |
| template <typename QueryP> |
| static OSReturn add_counter(QueryP query, HCOUNTER* counter, const char* counter_path, bool first_sample_on_init) { |
| assert(query != nullptr, "invariant"); |
| assert(counter != nullptr, "invariant"); |
| assert(counter_path != nullptr, "invariant"); |
| return add_counter(&query->query, counter, counter_path, first_sample_on_init); |
| } |
| |
| // if add_counter fails with OS_ERR, the performance counter might be disabled in the registry |
| static OSReturn add_counter(CounterQueryP query, const char* counter_path, bool first_sample_on_init = true) { |
| return add_counter(query, &query->counter, counter_path, first_sample_on_init); |
| } |
| |
| static OSReturn add_counter(MultiCounterQueryP query, int counter_idx, const char* counter_path, bool first_sample_on_init) { |
| assert(query != nullptr, "invariant"); |
| assert(counter_idx < query->noOfCounters, "invariant"); |
| assert(query->counters[counter_idx] == nullptr, "invariant"); |
| return add_counter(query, &query->counters[counter_idx], counter_path, first_sample_on_init); |
| } |
| |
| // Need to limit how often we update a 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.) |
| static const int min_update_interval_millis = 500; |
| |
| static int collect(UpdateQueryP query) { |
| assert(query != nullptr, "invariant"); |
| const s8 now = os::javaTimeNanos(); |
| if (nanos_to_millis(now - query->lastUpdate) > min_update_interval_millis) { |
| if (PdhDll::PdhCollectQueryData(query->pdh_query_handle) != ERROR_SUCCESS) { |
| return OS_ERR; |
| } |
| query->lastUpdate = now; |
| } |
| return OS_OK; |
| } |
| |
| template <typename QueryP> |
| static int collect(QueryP query) { |
| assert(query != nullptr, "invariant"); |
| return collect(&query->query); |
| } |
| |
| static int formatted_counter_value(HCOUNTER counter, DWORD format, PDH_FMT_COUNTERVALUE* const value) { |
| assert(value != nullptr, "invariant"); |
| return PdhDll::PdhGetFormattedCounterValue(counter, format, nullptr, value) != ERROR_SUCCESS ? OS_ERR : OS_OK; |
| } |
| |
| static int read_counter(CounterQueryP query, DWORD format, PDH_FMT_COUNTERVALUE* const value) { |
| assert(query != nullptr, "invariant"); |
| return formatted_counter_value(query->counter, format, value); |
| } |
| |
| static int read_counter(MultiCounterQueryP query, int counter_idx, DWORD format, PDH_FMT_COUNTERVALUE* const value) { |
| assert(query != nullptr, "invariant"); |
| assert(counter_idx < query->noOfCounters, "invariant"); |
| assert(query->counters[counter_idx] != nullptr, "invariant"); |
| return formatted_counter_value(query->counters[counter_idx], format, value); |
| } |
| |
| static int read_counter(ProcessQueryP query, int counter_idx, DWORD format, PDH_FMT_COUNTERVALUE* const value) { |
| assert(query != nullptr, "invariant"); |
| MultiCounterQueryP const current_query = &query->set.queries[query->process_idx]; |
| assert(current_query != nullptr, "invariant"); |
| return read_counter(current_query, counter_idx, format, value); |
| } |
| |
| /* |
| * The routine expands a process object path including a wildcard to fetch the list of process instances |
| * having the same name, i.e. "java" or rather the value of process_image_name. |
| * A tally of this list is returned to the caller. |
| */ |
| static int number_of_live_process_instances() { |
| char* buffer = nullptr; |
| DWORD size = 0; |
| // determine size |
| PDH_STATUS status = PdhDll::PdhExpandWildCardPath(nullptr, |
| pdh_process_instance_wildcard_IDProcess_counter, |
| buffer, |
| &size, |
| PDH_NOEXPANDCOUNTERS); |
| while (status == PDH_MORE_DATA) { |
| buffer = NEW_RESOURCE_ARRAY(char, size); |
| status = PdhDll::PdhExpandWildCardPath(nullptr, |
| pdh_process_instance_wildcard_IDProcess_counter, |
| buffer, |
| &size, |
| PDH_NOEXPANDCOUNTERS); |
| } |
| if (status != ERROR_SUCCESS) { |
| return OS_ERR; |
| } |
| // count the number of live process instances |
| int instances = 0; |
| const char* const end = buffer + size; |
| for (char* next = buffer; next != end && (*next != '\0'); next = &next[strlen(next) + 1], ++instances); |
| assert(instances > 0, "invariant"); |
| return instances; |
| } |
| |
| static PDH_STATUS pdh_process_idx_to_pid(HQUERY& pdh_query_handle, int idx, LONG* pid) { |
| assert(pid != nullptr, "invariant"); |
| char counter_path[PDH_MAX_COUNTER_PATH]; |
| jio_snprintf(counter_path, sizeof(counter_path) - 1, pdh_process_instance_IDProcess_counter_fmt, idx); |
| assert(strlen(counter_path) < sizeof(counter_path), "invariant"); |
| HCOUNTER counter = nullptr; |
| PDH_STATUS status = PdhDll::PdhAddCounter(pdh_query_handle, counter_path, 0, &counter); |
| if (status != ERROR_SUCCESS) { |
| close_query(&pdh_query_handle, &counter); |
| return status; |
| } |
| status = PdhDll::PdhCollectQueryData(pdh_query_handle); |
| if (status != ERROR_SUCCESS) { |
| close_query(nullptr, &counter); |
| return PDH_NO_DATA; |
| } |
| PDH_FMT_COUNTERVALUE counter_value; |
| status = formatted_counter_value(counter, PDH_FMT_LONG, &counter_value); |
| if (status != OS_OK) { |
| close_query(&pdh_query_handle, &counter); |
| return status; |
| } |
| *pid = counter_value.longValue; |
| close_query(nullptr, &counter); |
| return ERROR_SUCCESS; |
| } |
| |
| |
| /* |
| * Max process query index is derived from the total number of live process instances, seen |
| * as a snap-shot at the point of initialization, i.e. processes having the same name, e.g. "java". |
| * The total number of live processes includes this process and this number - 1 is the maximum index |
| * to be used in a process query. |
| */ |
| static int max_process_query_idx = 0; |
| |
| /* |
| * Working with the Process object and its related counters is inherently |
| * problematic when using the PDH API: |
| * |
| * A process is not primarily identified by the process id, but by an opaque |
| * index into a list maintained by the kernel. To distinguish which |
| * process instance is the intended target for a query, the PDH Process API demands, |
| * at time of registration, a string describing the target process name concatenated |
| * with the value for this index. For example: |
| * "\Process(java#0)", "\Process(java#1)", ... |
| * |
| * The bad part is that this list is constantly in-flux as |
| * processes are exiting. One consequence is that processes with indexes |
| * greater than the one that just terminated is now shifted down by one. |
| * For example: |
| * if \Process(java#1) exits, \Process(java#2) now becomes \Process(java#1), |
| * \Process(java#2) becomes \Process(java#1) ... |
| * |
| * To make matters even more exciting, an already registered query is not invalidated |
| * when the process list changes. Instead, the query will continue to work just as before, |
| * or at least, so it seems. |
| * Only, now, the query will read performance data from another process instance! |
| * That's right, the performance data is now read from the process that was shifted |
| * down by the kernel to occupy the index slot associated with our original registration. |
| * |
| * Solution: |
| * The #index identifier for a Process query can only decrease after process creation. |
| * |
| * We therefore create an array of counter queries for all process object instances |
| * up to and including ourselves: |
| * |
| * E.g. we come in as the third process instance (java#2), we then create and register |
| * queries for the following Process object instances: |
| * java#0, java#1, java#2 |
| * |
| * current_process_query_index() finds the "correct" pdh process query index by inspecting |
| * the pdh process list, at a particular instant, i.e. just before we issue the real process query. |
| * Of course, this is an inherently racy situation because the pdh process list can change at any time. |
| * We use current_process_query_index() to help keep the number of data errors low, |
| * where a data error is defined to be the result of using a stale index to query the wrong process. |
| * |
| * Ensure to call ensure_current_process_query_index() before every query involving Process object instance data. |
| * |
| * returns OS_ERR(-1) if anything goes wrong in the discovery process. |
| */ |
| |
| static int current_process_query_index(int previous_query_idx = 0) { |
| assert(max_process_query_idx >= 0, "invariant"); |
| assert(max_process_query_idx >= previous_query_idx, "invariant"); |
| assert(process_image_name != nullptr, "invariant"); |
| assert(pdh_process_instance_IDProcess_counter_fmt != nullptr, "invariant"); |
| int result = OS_ERR; |
| HQUERY tmp_pdh_query_handle = nullptr; |
| if (open_query(&tmp_pdh_query_handle) != OS_OK) { |
| return OS_ERR; |
| } |
| // We need to find the correct pdh process index corresponding to our process identifier (pid). |
| // Begin from the index that was valid at the time of the last query. If that index is no longer valid, |
| // it means the pdh process list has changed, i.e. because other processes with the same name as us have terminated. |
| // Seek downwards to find the updated, now downshifted, list index corresponding to our pid. |
| static const LONG current_pid = (LONG)os::current_process_id(); |
| const int start_idx = previous_query_idx != 0 ? previous_query_idx : max_process_query_idx; |
| for (int idx = start_idx; idx >= 0; --idx) { |
| LONG pid; |
| const PDH_STATUS status = pdh_process_idx_to_pid(tmp_pdh_query_handle, idx, &pid); |
| if (status == PDH_NO_DATA) { |
| // pdh process list has changed |
| continue; |
| } |
| if (status != ERROR_SUCCESS) { |
| // something went wrong, tmp_pdh_query_handle is already closed. |
| return OS_ERR; |
| } |
| if (current_pid == pid) { |
| result = idx; |
| break; |
| } |
| } |
| close_query(&tmp_pdh_query_handle, nullptr); |
| return result; |
| } |
| |
| static int ensure_current_process_query_index(ProcessQueryP query) { |
| assert(query != nullptr, "invariant"); |
| const int previous_query_idx = query->process_idx; |
| if (previous_query_idx == 0) { |
| return previous_query_idx; |
| } |
| const int current_query_idx = current_process_query_index(previous_query_idx); |
| if (current_query_idx == OS_ERR || current_query_idx >= query->set.size) { |
| return OS_ERR; |
| } |
| if (current_query_idx == previous_query_idx) { |
| return previous_query_idx; |
| } |
| assert(current_query_idx >= 0 && current_query_idx < query->set.size, "out of bounds!"); |
| while (current_query_idx < query->set.size - 1) { |
| const int new_size = --query->set.size; |
| close_query(&query->set.queries[new_size]); |
| } |
| assert(current_query_idx < query->set.size, "invariant"); |
| query->process_idx = current_query_idx; |
| return OS_OK; |
| } |
| |
| static MultiCounterQueryP current_process_query(ProcessQueryP query) { |
| assert(query != nullptr, "invariant"); |
| if (ensure_current_process_query_index(query) == OS_ERR) { |
| return nullptr; |
| } |
| assert(query->process_idx < query->set.size, "invariant"); |
| return &query->set.queries[query->process_idx]; |
| } |
| |
| static int collect(ProcessQueryP query) { |
| assert(query != nullptr, "invariant"); |
| MultiCounterQueryP current_query = current_process_query(query); |
| return current_query != nullptr ? collect(current_query) : OS_ERR; |
| } |
| |
| /* |
| * Construct a fully qualified PDH counter path. |
| * |
| * @param object_name a PDH Object string representation(required) |
| * @param counter_name a PDH Counter string representation(required) |
| * @param image_name a process image name string, ex. "java" (opt) |
| * @param instance an instance string, ex. "0", "1", ... (opt) |
| * @return the fully qualified PDH counter path. |
| * |
| * Caller will need a ResourceMark. |
| * |
| * (PdhMakeCounterPath() seems buggy on concatenating instances, hence this function instead) |
| */ |
| static const char* make_fully_qualified_counter_path(const char* object_name, |
| const char* counter_name, |
| const char* image_name = nullptr, |
| const char* instance = nullptr) { |
| assert(object_name != nullptr, "invariant"); |
| assert(counter_name != nullptr, "invariant"); |
| size_t counter_path_len = strlen(object_name) + strlen(counter_name); |
| |
| char* counter_path; |
| size_t jio_snprintf_result = 0; |
| if (image_name) { |
| /* |
| * For paths using the "Process" Object. |
| * |
| * Examples: |
| * form: "\object_name(image_name#instance)\counter_name" |
| * actual: "\Process(java#2)\ID Process" |
| */ |
| counter_path_len += PROCESS_OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; |
| counter_path_len += strlen(image_name); |
| /* |
| * image_name 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 != nullptr, "invariant"); |
| counter_path_len += strlen(instance); |
| counter_path = NEW_RESOURCE_ARRAY(char, counter_path_len + 1); |
| jio_snprintf_result = jio_snprintf(counter_path, |
| counter_path_len + 1, |
| PROCESS_OBJECT_WITH_INSTANCES_COUNTER_FMT, |
| object_name, |
| image_name, |
| instance, |
| counter_name); |
| } else { |
| if (instance) { |
| /* |
| * For paths where the Object has multiple instances. |
| * |
| * Examples: |
| * form: "\object_name(instance)\counter_name" |
| * actual: "\Processor(0)\% Privileged Time" |
| */ |
| counter_path_len += strlen(instance); |
| counter_path_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; |
| } else { |
| /* |
| * For "normal" paths. |
| * |
| * Examples: |
| * form: "\object_name\counter_name" |
| * actual: "\Memory\Available Mbytes" |
| */ |
| counter_path_len += OBJECT_COUNTER_FMT_LEN; |
| } |
| counter_path = NEW_RESOURCE_ARRAY(char, counter_path_len + 1); |
| if (instance) { |
| jio_snprintf_result = jio_snprintf(counter_path, |
| counter_path_len + 1, |
| OBJECT_WITH_INSTANCES_COUNTER_FMT, |
| object_name, |
| instance, |
| counter_name); |
| } else { |
| jio_snprintf_result = jio_snprintf(counter_path, |
| counter_path_len + 1, |
| OBJECT_COUNTER_FMT, |
| object_name, |
| counter_name); |
| } |
| } |
| assert(counter_path_len == jio_snprintf_result, "invariant"); |
| return counter_path; |
| } |
| |
| static void log_invalid_pdh_index(DWORD index) { |
| log_warning(os)("Unable to resolve PDH index: (%ld)", index); |
| log_warning(os)("Please check the registry if this performance object/counter is disabled"); |
| } |
| |
| static bool is_valid_pdh_index(DWORD index) { |
| DWORD dummy = 0; |
| if (PdhDll::PdhLookupPerfNameByIndex(nullptr, index, nullptr, &dummy) != PDH_MORE_DATA) { |
| log_invalid_pdh_index(index); |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * Maps an index to a resource area allocated string for the localized PDH artifact. |
| * |
| * Caller will need a ResourceMark. |
| * |
| * @param index the counter index as specified in the registry |
| * @param p_string pointer to a char* |
| * @return OS_OK if successful, OS_ERR on failure. |
| */ |
| static OSReturn lookup_name_by_index(DWORD index, char** p_string) { |
| assert(p_string != nullptr, "invariant"); |
| if (!is_valid_pdh_index(index)) { |
| return OS_ERR; |
| } |
| // determine size needed |
| DWORD size = 0; |
| PDH_STATUS status = PdhDll::PdhLookupPerfNameByIndex(nullptr, index, nullptr, &size); |
| assert(status == PDH_MORE_DATA, "invariant"); |
| *p_string = NEW_RESOURCE_ARRAY(char, size); |
| if (PdhDll::PdhLookupPerfNameByIndex(nullptr, index, *p_string, &size) != ERROR_SUCCESS) { |
| return OS_ERR; |
| } |
| if (0 == size || *p_string == nullptr) { |
| return OS_ERR; |
| } |
| // windows vista does not null-terminate the string (although the docs says it will) |
| (*p_string)[size - 1] = '\0'; |
| return OS_OK; |
| } |
| |
| static const char* copy_string_to_c_heap(const char* string) { |
| assert(string != nullptr, "invariant"); |
| const size_t len = strlen(string); |
| char* const cheap_allocated_string = NEW_C_HEAP_ARRAY(char, len + 1, mtInternal); |
| strncpy(cheap_allocated_string, string, len + 1); |
| return cheap_allocated_string; |
| } |
| |
| /* |
| * Maps a pdh artifact index to a resource area allocated string representing a localized name. |
| * |
| * Caller will need a ResourceMark. |
| * |
| * @param pdh_artifact_idx the counter index as specified in the registry |
| * @return localized pdh artifact string if successful, null on failure. |
| */ |
| static const char* pdh_localized_artifact(DWORD pdh_artifact_idx) { |
| char* pdh_localized_artifact_string = nullptr; |
| // get localized name for the pdh artifact idx |
| if (lookup_name_by_index(pdh_artifact_idx, &pdh_localized_artifact_string) != OS_OK) { |
| return nullptr; |
| } |
| return pdh_localized_artifact_string; |
| } |
| |
| /* |
| * Returns the PDH string identifying the current process image name. |
| * Use this prefix when getting counters from the PDH process object |
| * representing your process. |
| * Ex. "Process(java#0)\Virtual Bytes" - where "java" is the PDH process |
| * image description. |
| * |
| * Caller needs ResourceMark. |
| * |
| * @return the process image string description, null if the call failed. |
| */ |
| static const char* pdh_process_image_name() { |
| char* module_name = NEW_RESOURCE_ARRAY(char, MAX_PATH); |
| // Find our module name and use it to extract the image name used by PDH |
| DWORD getmfn_return = GetModuleFileName(nullptr, module_name, MAX_PATH); |
| if (getmfn_return >= MAX_PATH || 0 == getmfn_return) { |
| return nullptr; |
| } |
| if (os::get_last_error() == ERROR_INSUFFICIENT_BUFFER) { |
| return nullptr; |
| } |
| char* process_image_name = strrchr(module_name, '\\'); //drop path |
| process_image_name++; //skip slash |
| char* dot_pos = strrchr(process_image_name, '.'); //drop .exe |
| dot_pos[0] = '\0'; |
| return process_image_name; |
| } |
| |
| static void deallocate_pdh_constants() { |
| FREE_C_HEAP_ARRAY(char, process_image_name); |
| process_image_name = nullptr; |
| FREE_C_HEAP_ARRAY(char, pdh_process_instance_IDProcess_counter_fmt); |
| pdh_process_instance_IDProcess_counter_fmt = nullptr; |
| FREE_C_HEAP_ARRAY(char, pdh_process_instance_wildcard_IDProcess_counter); |
| pdh_process_instance_wildcard_IDProcess_counter = nullptr; |
| } |
| |
| static OSReturn allocate_pdh_constants() { |
| assert(process_image_name == nullptr, "invariant"); |
| const char* pdh_image_name = pdh_process_image_name(); |
| if (pdh_image_name == nullptr) { |
| return OS_ERR; |
| } |
| process_image_name = copy_string_to_c_heap(pdh_image_name); |
| |
| const char* pdh_localized_process_object = pdh_localized_artifact(PDH_PROCESS_IDX); |
| if (pdh_localized_process_object == nullptr) { |
| return OS_ERR; |
| } |
| |
| const char* pdh_localized_IDProcess_counter = pdh_localized_artifact(PDH_ID_PROCESS_IDX); |
| if (pdh_localized_IDProcess_counter == nullptr) { |
| return OS_ERR; |
| } |
| |
| const size_t id_process_base_length = strlen(process_image_name) + |
| strlen(pdh_localized_process_object) + |
| strlen(pdh_localized_IDProcess_counter); |
| |
| const size_t pdh_IDProcess_counter_fmt_len = id_process_base_length + |
| PROCESS_OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN + |
| 2; // "%d" |
| |
| assert(pdh_process_instance_IDProcess_counter_fmt == nullptr, "invariant"); |
| pdh_process_instance_IDProcess_counter_fmt = NEW_C_HEAP_ARRAY(char, pdh_IDProcess_counter_fmt_len + 1, mtInternal); |
| |
| /* "\Process(java#%d)\ID Process" */ |
| size_t len = jio_snprintf(pdh_process_instance_IDProcess_counter_fmt, |
| pdh_IDProcess_counter_fmt_len + 1, |
| PROCESS_OBJECT_WITH_INSTANCES_COUNTER_FMT, |
| pdh_localized_process_object, |
| process_image_name, |
| "%d", |
| pdh_localized_IDProcess_counter); |
| |
| assert(pdh_process_instance_IDProcess_counter_fmt != nullptr, "invariant"); |
| assert(len == pdh_IDProcess_counter_fmt_len, "invariant"); |
| |
| |
| const size_t pdh_IDProcess_wildcard_fmt_len = id_process_base_length + |
| PROCESS_OBJECT_WITH_INSTANCES_WILDCARD_FMT_LEN; |
| |
| assert(pdh_process_instance_wildcard_IDProcess_counter == nullptr, "invariant"); |
| pdh_process_instance_wildcard_IDProcess_counter = NEW_C_HEAP_ARRAY(char, pdh_IDProcess_wildcard_fmt_len + 1, mtInternal); |
| |
| /* "\Process(java*)\ID Process" */ |
| len = jio_snprintf(pdh_process_instance_wildcard_IDProcess_counter, |
| pdh_IDProcess_wildcard_fmt_len + 1, |
| PROCESS_OBJECT_WITH_INSTANCES_WILDCARD_FMT, |
| pdh_localized_process_object, |
| process_image_name, |
| pdh_localized_IDProcess_counter); |
| |
| assert(pdh_process_instance_wildcard_IDProcess_counter != nullptr, "invariant"); |
| assert(len == pdh_IDProcess_wildcard_fmt_len, "invariant"); |
| return OS_OK; |
| } |
| |
| /* |
| * Enuerate the Processor PDH object and returns a buffer containing the enumerated instances. |
| * Caller needs ResourceMark; |
| * |
| * @return buffer if successful, null on failure. |
| */ |
| static const char* enumerate_cpu_instances() { |
| char* processor; //'Processor' == PDH_PROCESSOR_IDX |
| if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { |
| return nullptr; |
| } |
| DWORD c_size = 0; |
| DWORD i_size = 0; |
| // enumerate all processors. |
| PDH_STATUS pdhStat = PdhDll::PdhEnumObjectItems(nullptr, // reserved |
| nullptr, // local machine |
| processor, // object to enumerate |
| nullptr, |
| &c_size, |
| nullptr, // instance buffer is null and |
| &i_size, // pass 0 length in order to get the required size |
| PERF_DETAIL_WIZARD, // counter detail level |
| 0); |
| if (PdhDll::PdhStatusFail((pdhStat))) { |
| return nullptr; |
| } |
| char* const instances = NEW_RESOURCE_ARRAY(char, i_size); |
| c_size = 0; |
| pdhStat = PdhDll::PdhEnumObjectItems(nullptr, // reserved |
| nullptr, // local machine |
| processor, // object to enumerate |
| nullptr, |
| &c_size, |
| instances, // now instance buffer is allocated to be filled in |
| &i_size, // and the required size is known |
| PERF_DETAIL_WIZARD, // counter detail level |
| 0); |
| return PdhDll::PdhStatusFail(pdhStat) ? nullptr : instances; |
| } |
| |
| static int count_logical_cpus(const char* instances) { |
| assert(instances != nullptr, "invariant"); |
| // count logical instances. |
| DWORD count; |
| char* tmp; |
| for (count = 0, tmp = const_cast<char*>(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], count++); |
| // PDH reports an instance for each logical processor plus an instance for the total (_Total) |
| assert(count == os::processor_count() + 1, "invalid enumeration!"); |
| return count - 1; |
| } |
| |
| static int number_of_logical_cpus() { |
| static int numberOfCPUS = 0; |
| if (numberOfCPUS == 0) { |
| const char* instances = enumerate_cpu_instances(); |
| if (instances == nullptr) { |
| return OS_ERR; |
| } |
| numberOfCPUS = count_logical_cpus(instances); |
| } |
| return numberOfCPUS; |
| } |
| |
| static double cpu_factor() { |
| static DWORD numCpus = 0; |
| static double cpuFactor = .0; |
| if (numCpus == 0) { |
| numCpus = number_of_logical_cpus(); |
| assert(os::processor_count() <= (int)numCpus, "invariant"); |
| cpuFactor = numCpus * 100; |
| } |
| return cpuFactor; |
| } |
| |
| static void log_error_message_on_no_PDH_artifact(const char* counter_path) { |
| log_warning(os)("Unable to register PDH query for \"%s\"", counter_path); |
| log_warning(os)("Please check the registry if this performance object/counter is disabled"); |
| } |
| |
| static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_counter_idx) { |
| assert(query != nullptr, "invariant"); |
| assert(query->counters != nullptr, "invariant"); |
| char* processor; //'Processor' == PDH_PROCESSOR_IDX |
| if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { |
| return OS_ERR; |
| } |
| char* counter_name = nullptr; |
| if (lookup_name_by_index(pdh_counter_idx, &counter_name) != OS_OK) { |
| return OS_ERR; |
| } |
| if (query->query.pdh_query_handle == nullptr) { |
| if (open_query(query) != OS_OK) { |
| return OS_ERR; |
| } |
| } |
| assert(query->query.pdh_query_handle != nullptr, "invariant"); |
| size_t counter_len = strlen(processor); |
| counter_len += strlen(counter_name); |
| counter_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; // "\\%s(%s)\\%s" |
| const char* instances = enumerate_cpu_instances(); |
| DWORD index = 0; |
| for (char* tmp = const_cast<char*>(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], index++) { |
| const size_t tmp_len = strlen(tmp); |
| char* counter_path = NEW_RESOURCE_ARRAY(char, counter_len + tmp_len + 1); |
| const size_t jio_snprintf_result = jio_snprintf(counter_path, |
| counter_len + tmp_len + 1, |
| OBJECT_WITH_INSTANCES_COUNTER_FMT, |
| processor, |
| tmp, // instance "0", "1", .."_Total" |
| counter_name); |
| assert(counter_len + tmp_len == jio_snprintf_result, "invariant"); |
| if (add_counter(query, &query->counters[index], counter_path, false) != OS_OK) { |
| // performance counter is disabled in registry and not accessible via PerfLib |
| log_error_message_on_no_PDH_artifact(counter_path); |
| // return OS_OK to have the system continue to run without the missing counter |
| return OS_OK; |
| } |
| } |
| // Query once to initialize the counters which require at least two samples |
| // (like the % CPU usage) to calculate correctly. |
| return PdhDll::PdhCollectQueryData(query->query.pdh_query_handle) != ERROR_SUCCESS ? OS_ERR : OS_OK; |
| } |
| |
| static int initialize_cpu_query(MultiCounterQueryP query) { |
| assert(query != nullptr, "invariant"); |
| assert(!query->initialized, "invariant"); |
| const int logical_cpu_count = number_of_logical_cpus(); |
| assert(logical_cpu_count >= os::processor_count(), "invariant"); |
| // we also add another counter for instance "_Total" |
| allocate_counters(query, logical_cpu_count + 1); |
| assert(query->noOfCounters == logical_cpu_count + 1, "invariant"); |
| if (initialize_cpu_query_counters(query, PDH_PROCESSOR_TIME_IDX) != OS_OK) { |
| return OS_ERR; |
| } |
| query->initialized = true; |
| return OS_OK; |
| } |
| |
| static int initialize_query(CounterQueryP query, DWORD pdh_object_idx, DWORD pdh_counter_idx) { |
| assert(query != nullptr, "invariant"); |
| assert(!query->initialized, "invariant"); |
| if (!((is_valid_pdh_index(pdh_object_idx) && is_valid_pdh_index(pdh_counter_idx)))) { |
| return OS_ERR; |
| } |
| const char* object = pdh_localized_artifact(pdh_object_idx); |
| assert(object != nullptr, "invariant"); |
| const char* counter = pdh_localized_artifact(pdh_counter_idx); |
| assert(counter != nullptr, "invariant"); |
| const char* counter_path = make_fully_qualified_counter_path(object, counter); |
| assert(counter_path != nullptr, "invariant"); |
| if (add_counter(query, counter_path, true) != OS_OK) { |
| return OS_ERR; |
| } |
| query->initialized = true; |
| return OS_OK; |
| } |
| |
| static int initialize_context_switches_query(CounterQueryP query) { |
| return initialize_query(query, PDH_SYSTEM_IDX, PDH_CONTEXT_SWITCH_RATE_IDX); |
| } |
| |
| static ProcessQueryP create_process_query() { |
| const int current_process_query_idx = current_process_query_index(); |
| if (current_process_query_idx == OS_ERR) { |
| return nullptr; |
| } |
| ProcessQueryP const query = NEW_C_HEAP_OBJ(ProcessQueryS, mtInternal); |
| memset(query, 0, sizeof(ProcessQueryS)); |
| query->process_idx = current_process_query_idx; |
| const int size = current_process_query_idx + 1; |
| query->set.queries = NEW_C_HEAP_ARRAY(MultiCounterQueryS, size, mtInternal); |
| memset(query->set.queries, 0, sizeof(MultiCounterQueryS) * size); |
| query->set.size = size; |
| return query; |
| } |
| |
| static int initialize_process_counter(ProcessQueryP process_query, int counter_idx, DWORD pdh_counter_idx) { |
| char* localized_process_object; |
| if (lookup_name_by_index(PDH_PROCESS_IDX, &localized_process_object) != OS_OK) { |
| return OS_ERR; |
| } |
| assert(localized_process_object != nullptr, "invariant"); |
| char* localized_counter_name; |
| if (lookup_name_by_index(pdh_counter_idx, &localized_counter_name) != OS_OK) { |
| return OS_ERR; |
| } |
| assert(localized_counter_name != nullptr, "invariant"); |
| for (int i = 0; i < process_query->set.size; ++i) { |
| char instanceIndexBuffer[32]; |
| const char* counter_path = make_fully_qualified_counter_path(localized_process_object, |
| localized_counter_name, |
| process_image_name, |
| itoa(i, instanceIndexBuffer, 10)); |
| assert(counter_path != nullptr, "invariant"); |
| MultiCounterQueryP const query = &process_query->set.queries[i]; |
| if (add_counter(query, counter_idx, counter_path, true) != OS_OK) { |
| return OS_ERR; |
| } |
| if (counter_idx + 1 == query->noOfCounters) { |
| // last counter in query implies query initialized |
| query->initialized = true; |
| } |
| } |
| return OS_OK; |
| } |
| |
| static int initialize_process_query(ProcessQueryP query) { |
| assert(query != nullptr, "invariant"); |
| assert(!query->set.initialized, "invariant"); |
| allocate_counters(query, 2); |
| if (initialize_process_counter(query, 0, PDH_PROCESSOR_TIME_IDX) != OS_OK) { |
| return OS_ERR; |
| } |
| if (initialize_process_counter(query, 1, PDH_PRIV_PROCESSOR_TIME_IDX) != OS_OK) { |
| return OS_ERR; |
| } |
| query->set.initialized = true; |
| return OS_OK; |
| } |
| |
| static int reference_count = 0; |
| static bool pdh_initialized = false; |
| |
| class PdhMutex : public StackObj { |
| private: |
| static Semaphore _semaphore; |
| public: |
| PdhMutex() { |
| _semaphore.wait(); |
| } |
| ~PdhMutex() { |
| _semaphore.signal(); |
| } |
| }; |
| |
| Semaphore PdhMutex::_semaphore(1); |
| |
| static void on_initialization_failure() { |
| // still holder of mutex |
| assert(max_process_query_idx == 0, "invariant"); |
| deallocate_pdh_constants(); |
| --reference_count; |
| PdhDll::PdhDetach(); |
| } |
| |
| static OSReturn initialize() { |
| // still holder of mutex |
| ResourceMark rm; |
| if (!PdhDll::PdhAttach()) { |
| return OS_ERR; |
| } |
| if (allocate_pdh_constants() != OS_OK) { |
| on_initialization_failure(); |
| return OS_ERR; |
| } |
| // Take a snapshot of the current number of live processes (including ourselves) |
| // with the same name, e.g. "java", in order to derive a value for max_process_query_idx. |
| const int process_instance_count = number_of_live_process_instances(); |
| if (process_instance_count == OS_ERR) { |
| on_initialization_failure(); |
| return OS_ERR; |
| } |
| assert(process_instance_count > 0, "invariant"); |
| max_process_query_idx = process_instance_count - 1; |
| return OS_OK; |
| } |
| |
| /* |
| * Helper to initialize the PDH library, function pointers, constants and counters. |
| * |
| * Reference counting allows for unloading of pdh.dll granted all sessions use the pair: |
| * |
| * pdh_acquire(); |
| * pdh_release(); |
| * |
| * @return OS_OK if successful, OS_ERR on failure. |
| */ |
| static OSReturn pdh_acquire() { |
| PdhMutex mutex; |
| reference_count++; |
| if (pdh_initialized) { |
| return OS_OK; |
| } |
| const OSReturn status = initialize(); |
| pdh_initialized = status == OS_OK; |
| return status; |
| } |
| |
| static void pdh_release() { |
| PdhMutex mutex; |
| if (1 == reference_count--) { |
| deallocate_pdh_constants(); |
| PdhDll::PdhDetach(); |
| pdh_initialized = false; |
| } |
| } |
| |
| class CPUPerformanceInterface::CPUPerformance : public CHeapObj<mtInternal> { |
| friend class CPUPerformanceInterface; |
| private: |
| CounterQueryP _context_switches; |
| ProcessQueryP _process_cpu_load; |
| MultiCounterQueryP _machine_cpu_load; |
| |
| int cpu_load(int which_logical_cpu, double* cpu_load); |
| int context_switch_rate(double* rate); |
| int cpu_load_total_process(double* cpu_load); |
| int cpu_loads_process(double* jvm_user_load, double* jvm_kernel_load, double* system_total_load); |
| CPUPerformance(); |
| ~CPUPerformance(); |
| bool initialize(); |
| }; |
| |
| CPUPerformanceInterface::CPUPerformance::CPUPerformance() : _context_switches(nullptr), _process_cpu_load(nullptr), _machine_cpu_load(nullptr) {} |
| |
| bool CPUPerformanceInterface::CPUPerformance::initialize() { |
| if (pdh_acquire() != OS_OK) { |
| return false; |
| } |
| _context_switches = create_counter_query(); |
| assert(_context_switches != nullptr, "invariant"); |
| if (initialize_context_switches_query(_context_switches) != OS_OK) { |
| return false; |
| } |
| assert(_context_switches->initialized, "invariant"); |
| _process_cpu_load = create_process_query(); |
| if (_process_cpu_load == nullptr) { |
| return false; |
| } |
| if (initialize_process_query(_process_cpu_load) != OS_OK) { |
| return false; |
| } |
| assert(_process_cpu_load->set.initialized, "invariant"); |
| _machine_cpu_load = create_multi_counter_query(); |
| assert(_machine_cpu_load != nullptr, "invariant"); |
| if (initialize_cpu_query(_machine_cpu_load) != OS_OK) { |
| return false; |
| } |
| assert(_machine_cpu_load->initialized, "invariant"); |
| return true; |
| } |
| |
| CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { |
| if (_context_switches != nullptr) { |
| destroy(_context_switches); |
| _context_switches = nullptr; |
| } |
| if (_process_cpu_load != nullptr) { |
| destroy(_process_cpu_load); |
| _process_cpu_load = nullptr; |
| } |
| if (_machine_cpu_load != nullptr) { |
| destroy(_machine_cpu_load); |
| _machine_cpu_load = nullptr; |
| } |
| pdh_release(); |
| } |
| |
| CPUPerformanceInterface::CPUPerformanceInterface() : _impl(nullptr) {} |
| |
| bool CPUPerformanceInterface::initialize() { |
| _impl = new CPUPerformanceInterface::CPUPerformance(); |
| return _impl->initialize(); |
| } |
| |
| CPUPerformanceInterface::~CPUPerformanceInterface() { |
| if (_impl != nullptr) { |
| delete _impl; |
| } |
| } |
| |
| int CPUPerformanceInterface::cpu_load(int which_logical_cpu, double* cpu_load) const { |
| return _impl->cpu_load(which_logical_cpu, cpu_load); |
| } |
| |
| int CPUPerformanceInterface::context_switch_rate(double* rate) const { |
| return _impl->context_switch_rate(rate); |
| } |
| |
| int CPUPerformanceInterface::cpu_load_total_process(double* cpu_load) const { |
| return _impl->cpu_load_total_process(cpu_load); |
| } |
| |
| int CPUPerformanceInterface::cpu_loads_process(double* jvm_user_load, |
| double* jvm_kernel_load, |
| double* system_total_load) const { |
| return _impl->cpu_loads_process(jvm_user_load, jvm_kernel_load, system_total_load); |
| } |
| |
| int CPUPerformanceInterface::CPUPerformance::cpu_load(int which_logical_cpu, double* cpu_load) { |
| *cpu_load = .0; |
| if (_machine_cpu_load == nullptr || !_machine_cpu_load->initialized) { |
| return OS_ERR; |
| } |
| assert(which_logical_cpu < _machine_cpu_load->noOfCounters, "invariant"); |
| if (collect(_machine_cpu_load) != OS_OK) { |
| return OS_ERR; |
| } |
| // -1 is total (all cpus) |
| const int counter_idx = -1 == which_logical_cpu ? _machine_cpu_load->noOfCounters - 1 : which_logical_cpu; |
| PDH_FMT_COUNTERVALUE counter_value; |
| if (read_counter(_machine_cpu_load, counter_idx, PDH_FMT_DOUBLE, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| *cpu_load = counter_value.doubleValue / 100; |
| return OS_OK; |
| } |
| |
| int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* cpu_load) { |
| *cpu_load = .0; |
| if (_process_cpu_load == nullptr || !_process_cpu_load->set.initialized) { |
| return OS_ERR; |
| } |
| if (collect(_process_cpu_load) != OS_OK) { |
| return OS_ERR; |
| } |
| PDH_FMT_COUNTERVALUE counter_value; |
| if (read_counter(_process_cpu_load, 0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| double process_load = counter_value.doubleValue / cpu_factor(); |
| process_load = MIN2<double>(1, process_load); |
| process_load = MAX2<double>(0, process_load); |
| *cpu_load = process_load; |
| return OS_OK; |
| } |
| |
| int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* jvm_user_load, |
| double* jvm_kernel_load, |
| double* system_total_load) { |
| assert(jvm_user_load != nullptr, "jvm_user_load is null!"); |
| assert(jvm_kernel_load != nullptr, "jvm_kernel_load is null!"); |
| assert(system_total_load != nullptr, "system_total_load is null!"); |
| *jvm_user_load = .0; |
| *jvm_kernel_load = .0; |
| *system_total_load = .0; |
| |
| if (_process_cpu_load == nullptr || !_process_cpu_load->set.initialized) { |
| return OS_ERR; |
| } |
| if (collect(_process_cpu_load) != OS_OK) { |
| return OS_ERR; |
| } |
| double process_load = .0; |
| PDH_FMT_COUNTERVALUE counter_value; |
| // Read PDH_PROCESSOR_TIME_IDX as counter_idx == 0 |
| if (read_counter(_process_cpu_load, 0, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| process_load = counter_value.doubleValue / cpu_factor(); |
| process_load = MIN2<double>(1, process_load); |
| process_load = MAX2<double>(0, process_load); |
| // Read PDH_PRIV_PROCESSOR_TIME_IDX as counter_idx == 1 |
| if (read_counter(_process_cpu_load, 1, PDH_FMT_DOUBLE | PDH_FMT_NOCAP100, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| double process_kernel_load = counter_value.doubleValue / cpu_factor(); |
| process_kernel_load = MIN2<double>(1, process_kernel_load); |
| process_kernel_load = MAX2<double>(0, process_kernel_load); |
| *jvm_kernel_load = process_kernel_load; |
| |
| double user_load = process_load - process_kernel_load; |
| user_load = MIN2<double>(1, user_load); |
| user_load = MAX2<double>(0, user_load); |
| *jvm_user_load = user_load; |
| if (collect(_machine_cpu_load) != OS_OK) { |
| return OS_ERR; |
| } |
| // Read PDH_PROCESSOR_IDX as counter_idx == _machine_cpu_load->noOfCounters - 1 |
| if (read_counter(_machine_cpu_load, _machine_cpu_load->noOfCounters - 1, PDH_FMT_DOUBLE, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| double machine_load = counter_value.doubleValue / 100; |
| assert(machine_load >= 0, "machine_load is negative!"); |
| // clamp at user+system and 1.0 |
| if (*jvm_kernel_load + *jvm_user_load > machine_load) { |
| machine_load = MIN2(*jvm_kernel_load + *jvm_user_load, 1.0); |
| } |
| *system_total_load = machine_load; |
| return OS_OK; |
| } |
| |
| int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { |
| assert(rate != nullptr, "invariant"); |
| *rate = .0; |
| if (_context_switches == nullptr || !_context_switches->initialized) { |
| return OS_ERR; |
| } |
| if (collect(_context_switches) != OS_OK) { |
| return OS_ERR; |
| } |
| PDH_FMT_COUNTERVALUE counter_value; |
| if (read_counter(_context_switches, PDH_FMT_DOUBLE, &counter_value) != OS_OK) { |
| return OS_ERR; |
| } |
| *rate = counter_value.doubleValue; |
| return OS_OK; |
| } |
| |
| class SystemProcessInterface::SystemProcesses : public CHeapObj<mtInternal> { |
| friend class SystemProcessInterface; |
| private: |
| class ProcessIterator : public CHeapObj<mtInternal> { |
| friend class SystemProcessInterface::SystemProcesses; |
| private: |
| HANDLE _hProcessSnap; |
| PROCESSENTRY32 _pe32; |
| BOOL _valid; |
| char _exePath[MAX_PATH]; |
| ProcessIterator(); |
| ~ProcessIterator(); |
| bool initialize(); |
| |
| int current(SystemProcess* const process_info); |
| int next_process(); |
| bool is_valid() const { return _valid != FALSE; } |
| char* allocate_string(const char* str) const; |
| int snapshot(); |
| }; |
| |
| ProcessIterator* _iterator; |
| SystemProcesses(); |
| ~SystemProcesses(); |
| bool initialize(); |
| |
| // information about system processes |
| int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; |
| }; |
| |
| SystemProcessInterface::SystemProcesses::ProcessIterator::ProcessIterator() { |
| _hProcessSnap = INVALID_HANDLE_VALUE; |
| _valid = FALSE; |
| _pe32.dwSize = sizeof(PROCESSENTRY32); |
| } |
| |
| bool SystemProcessInterface::SystemProcesses::ProcessIterator::initialize() { |
| return true; |
| } |
| |
| int SystemProcessInterface::SystemProcesses::ProcessIterator::snapshot() { |
| // take snapshot of all process in the system |
| _hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); |
| if (_hProcessSnap == INVALID_HANDLE_VALUE) { |
| return OS_ERR; |
| } |
| // step to first process |
| _valid = Process32First(_hProcessSnap, &_pe32); |
| return is_valid() ? OS_OK : OS_ERR; |
| } |
| |
| SystemProcessInterface::SystemProcesses::ProcessIterator::~ProcessIterator() { |
| if (_hProcessSnap != INVALID_HANDLE_VALUE) { |
| CloseHandle(_hProcessSnap); |
| } |
| } |
| |
| int SystemProcessInterface::SystemProcesses::ProcessIterator::current(SystemProcess* process_info) { |
| assert(is_valid(), "no current process to be fetched!"); |
| assert(process_info != nullptr, "process_info is null!"); |
| char* exePath = nullptr; |
| HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, _pe32.th32ProcessID); |
| if (hProcess != nullptr) { |
| HMODULE hMod; |
| DWORD cbNeeded; |
| if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded) != 0) { |
| if (GetModuleFileNameExA(hProcess, hMod, _exePath, sizeof(_exePath)) != 0) { |
| exePath = _exePath; |
| } |
| } |
| CloseHandle (hProcess); |
| } |
| process_info->set_pid((int)_pe32.th32ProcessID); |
| process_info->set_name(allocate_string(_pe32.szExeFile)); |
| process_info->set_path(allocate_string(exePath)); |
| return OS_OK; |
| } |
| |
| char* SystemProcessInterface::SystemProcesses::ProcessIterator::allocate_string(const char* str) const { |
| return str != nullptr ? os::strdup_check_oom(str, mtInternal) : nullptr; |
| } |
| |
| int SystemProcessInterface::SystemProcesses::ProcessIterator::next_process() { |
| _valid = Process32Next(_hProcessSnap, &_pe32); |
| return OS_OK; |
| } |
| |
| SystemProcessInterface::SystemProcesses::SystemProcesses() : _iterator(nullptr) {} |
| |
| bool SystemProcessInterface::SystemProcesses::initialize() { |
| _iterator = new SystemProcessInterface::SystemProcesses::ProcessIterator(); |
| return _iterator->initialize(); |
| } |
| |
| SystemProcessInterface::SystemProcesses::~SystemProcesses() { |
| if (_iterator != nullptr) { |
| delete _iterator; |
| } |
| } |
| |
| int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, |
| int* no_of_sys_processes) const { |
| assert(system_processes != nullptr, "system_processes pointer is null!"); |
| assert(no_of_sys_processes != nullptr, "system_processes counter pointers is null!"); |
| assert(_iterator != nullptr, "iterator is null!"); |
| |
| // initialize pointers |
| *no_of_sys_processes = 0; |
| *system_processes = nullptr; |
| |
| // take process snapshot |
| if (_iterator->snapshot() != OS_OK) { |
| return OS_ERR; |
| } |
| |
| while (_iterator->is_valid()) { |
| SystemProcess* tmp = new SystemProcess(); |
| _iterator->current(tmp); |
| |
| //if already existing head |
| if (*system_processes != nullptr) { |
| //move "first to second" |
| tmp->set_next(*system_processes); |
| } |
| // new head |
| *system_processes = tmp; |
| // increment |
| (*no_of_sys_processes)++; |
| // step forward |
| _iterator->next_process(); |
| } |
| return OS_OK; |
| } |
| |
| int SystemProcessInterface::system_processes(SystemProcess** system_procs, |
| int* no_of_sys_processes) const { |
| return _impl->system_processes(system_procs, no_of_sys_processes); |
| } |
| |
| SystemProcessInterface::SystemProcessInterface() : _impl(nullptr) {} |
| |
| bool SystemProcessInterface::initialize() { |
| _impl = new SystemProcessInterface::SystemProcesses(); |
| return _impl->initialize(); |
| } |
| |
| SystemProcessInterface::~SystemProcessInterface() { |
| if (_impl != nullptr) { |
| delete _impl; |
| } |
| } |
| |
| CPUInformationInterface::CPUInformationInterface() : _cpu_info(nullptr) {} |
| |
| bool CPUInformationInterface::initialize() { |
| _cpu_info = new CPUInformation(); |
| VM_Version::initialize_cpu_information(); |
| _cpu_info->set_number_of_hardware_threads(VM_Version::number_of_threads()); |
| _cpu_info->set_number_of_cores(VM_Version::number_of_cores()); |
| _cpu_info->set_number_of_sockets(VM_Version::number_of_sockets()); |
| _cpu_info->set_cpu_name(VM_Version::cpu_name()); |
| _cpu_info->set_cpu_description(VM_Version::cpu_description()); |
| return true; |
| } |
| |
| CPUInformationInterface::~CPUInformationInterface() { |
| if (_cpu_info != nullptr) { |
| FREE_C_HEAP_ARRAY(char, _cpu_info->cpu_name()); |
| _cpu_info->set_cpu_name(nullptr); |
| FREE_C_HEAP_ARRAY(char, _cpu_info->cpu_description()); |
| _cpu_info->set_cpu_description(nullptr); |
| delete _cpu_info; |
| } |
| } |
| |
| int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { |
| if (nullptr == _cpu_info) { |
| return OS_ERR; |
| } |
| cpu_info = *_cpu_info; // shallow copy assignment |
| return OS_OK; |
| } |
| |
| class NetworkPerformanceInterface::NetworkPerformance : public CHeapObj<mtInternal> { |
| friend class NetworkPerformanceInterface; |
| private: |
| bool _iphlp_attached; |
| |
| NetworkPerformance(); |
| NONCOPYABLE(NetworkPerformance); |
| bool initialize(); |
| ~NetworkPerformance(); |
| int network_utilization(NetworkInterface** network_interfaces) const; |
| }; |
| |
| NetworkPerformanceInterface::NetworkPerformance::NetworkPerformance() : _iphlp_attached(false) {} |
| |
| bool NetworkPerformanceInterface::NetworkPerformance::initialize() { |
| _iphlp_attached = IphlpDll::IphlpAttach(); |
| return _iphlp_attached; |
| } |
| |
| NetworkPerformanceInterface::NetworkPerformance::~NetworkPerformance() { |
| if (_iphlp_attached) { |
| IphlpDll::IphlpDetach(); |
| } |
| } |
| |
| int NetworkPerformanceInterface::NetworkPerformance::network_utilization(NetworkInterface** network_interfaces) const { |
| MIB_IF_TABLE2* table; |
| |
| if (IphlpDll::GetIfTable2(&table) != NO_ERROR) { |
| return OS_ERR; |
| } |
| |
| NetworkInterface* ret = nullptr; |
| for (ULONG i = 0; i < table->NumEntries; ++i) { |
| if (table->Table[i].InterfaceAndOperStatusFlags.FilterInterface) { |
| continue; |
| } |
| |
| char buf[256]; |
| if (WideCharToMultiByte(CP_UTF8, 0, table->Table[i].Description, -1, buf, sizeof(buf), nullptr, nullptr) == 0) { |
| continue; |
| } |
| |
| NetworkInterface* cur = new NetworkInterface(buf, table->Table[i].InOctets, table->Table[i].OutOctets, ret); |
| ret = cur; |
| } |
| |
| IphlpDll::FreeMibTable(table); |
| *network_interfaces = ret; |
| |
| return OS_OK; |
| } |
| |
| NetworkPerformanceInterface::NetworkPerformanceInterface() : _impl(nullptr) {} |
| |
| NetworkPerformanceInterface::~NetworkPerformanceInterface() { |
| if (_impl != nullptr) { |
| delete _impl; |
| } |
| } |
| |
| bool NetworkPerformanceInterface::initialize() { |
| _impl = new NetworkPerformanceInterface::NetworkPerformance(); |
| return _impl->initialize(); |
| } |
| |
| int NetworkPerformanceInterface::network_utilization(NetworkInterface** network_interfaces) const { |
| return _impl->network_utilization(network_interfaces); |
| } |