/* * Copyright (c) 2012, 2021, 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. *
*/
/* * 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
*/ staticconst DWORD PDH_PROCESSOR_IDX = 238; staticconst DWORD PDH_PROCESSOR_TIME_IDX = 6; staticconst DWORD PDH_PRIV_PROCESSOR_TIME_IDX = 144; staticconst DWORD PDH_PROCESS_IDX = 230; staticconst DWORD PDH_ID_PROCESS_IDX = 784; staticconst DWORD PDH_CONTEXT_SWITCH_RATE_IDX = 146; staticconst DWORD PDH_SYSTEM_IDX = 2;
/* pdh string constants built up from fmts on initialization */ staticconstchar* process_image_name = NULL; // e.g. "java" but could have another image name staticchar* pdh_process_instance_IDProcess_counter_fmt = NULL; // "\Process(java#%d)\ID Process" */ staticchar* pdh_process_instance_wildcard_IDProcess_counter = NULL; // "\Process(java*)\ID Process" */
/* * Structs for PDH queries.
*/ typedefstruct {
HQUERY pdh_query_handle;
s8 lastUpdate; // Last time query was updated.
} UpdateQueryS, *UpdateQueryP;
static OSReturn add_counter(UpdateQueryP query, HCOUNTER* counter, constchar* counter_path, bool first_sample_on_init) {
assert(query != NULL, "invariant");
assert(counter != NULL, "invariant");
assert(counter_path != NULL, "invariant"); if (query->pdh_query_handle == NULL) { if (open_query(query) != OS_OK) { return OS_ERR;
}
}
assert(query->pdh_query_handle != NULL, "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;
}
// 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.) staticconstint min_update_interval_millis = 500;
staticint read_counter(CounterQueryP query, DWORD format, PDH_FMT_COUNTERVALUE* const value) {
assert(query != NULL, "invariant"); return formatted_counter_value(query->counter, format, value);
}
staticint read_counter(MultiCounterQueryP query, int counter_idx, DWORD format, PDH_FMT_COUNTERVALUE* const value) {
assert(query != NULL, "invariant");
assert(counter_idx < query->noOfCounters, "invariant");
assert(query->counters[counter_idx] != NULL, "invariant"); return formatted_counter_value(query->counters[counter_idx], format, value);
}
staticint read_counter(ProcessQueryP query, int counter_idx, DWORD format, PDH_FMT_COUNTERVALUE* const value) {
assert(query != NULL, "invariant");
MultiCounterQueryP const current_query = &query->set.queries[query->process_idx];
assert(current_query != NULL, "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.
*/ staticint number_of_live_process_instances() { char* buffer = NULL;
DWORD size = 0; // determine size
PDH_STATUS status = PdhDll::PdhExpandWildCardPath(NULL,
pdh_process_instance_wildcard_IDProcess_counter,
buffer,
&size,
PDH_NOEXPANDCOUNTERS); while (status == PDH_MORE_DATA) {
buffer = NEW_RESOURCE_ARRAY(char, size);
status = PdhDll::PdhExpandWildCardPath(NULL,
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; constchar* const end = buffer + size; for (char* next = buffer; next != end && (*next != '\0'); next = &next[strlen(next) + 1], ++instances);
assert(instances > 0, "invariant"); return instances;
}
/* * 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.
*/ staticint 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.
*/
staticint 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 != NULL, "invariant");
assert(pdh_process_instance_IDProcess_counter_fmt != NULL, "invariant"); int result = OS_ERR;
HQUERY tmp_pdh_query_handle = NULL; 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. staticconstLONG current_pid = (LONG)os::current_process_id(); constint 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, NULL); return result;
}
/* * 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)
*/ staticconstchar* make_fully_qualified_counter_path(constchar* object_name, constchar* counter_name, constchar* image_name = NULL, constchar* instance = NULL) {
assert(object_name != NULL, "invariant");
assert(counter_name != NULL, "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 != NULL, "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;
}
staticvoid 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");
}
/* * 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 != NULL, "invariant"); if (!is_valid_pdh_index(index)) { return OS_ERR;
} // determine size needed
DWORD size = 0;
PDH_STATUS status = PdhDll::PdhLookupPerfNameByIndex(NULL, index, NULL, &size);
assert(status == PDH_MORE_DATA, "invariant");
*p_string = NEW_RESOURCE_ARRAY(char, size); if (PdhDll::PdhLookupPerfNameByIndex(NULL, index, *p_string, &size) != ERROR_SUCCESS) { return OS_ERR;
} if (0 == size || *p_string == NULL) { 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;
}
staticconstchar* copy_string_to_c_heap(constchar* string) {
assert(string != NULL, "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.
*/ staticconstchar* pdh_localized_artifact(DWORD pdh_artifact_idx) { char* pdh_localized_artifact_string = NULL; // get localized name for the pdh artifact idx if (lookup_name_by_index(pdh_artifact_idx, &pdh_localized_artifact_string) != OS_OK) { return NULL;
} 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.
*/ staticconstchar* 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(NULL, module_name, MAX_PATH); if (getmfn_return >= MAX_PATH || 0 == getmfn_return) { return NULL;
} if (os::get_last_error() == ERROR_INSUFFICIENT_BUFFER) { return NULL;
} 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;
}
staticvoid log_error_message_on_no_PDH_artifact(constchar* 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");
}
staticint initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_counter_idx) {
assert(query != NULL, "invariant");
assert(query->counters != NULL, "invariant"); char* processor; //'Processor' == PDH_PROCESSOR_IDX if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { return OS_ERR;
} char* counter_name = NULL; if (lookup_name_by_index(pdh_counter_idx, &counter_name) != OS_OK) { return OS_ERR;
} if (query->query.pdh_query_handle == NULL) { if (open_query(query) != OS_OK) { return OS_ERR;
}
}
assert(query->query.pdh_query_handle != NULL, "invariant");
size_t counter_len = strlen(processor);
counter_len += strlen(counter_name);
counter_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; // "\\%s(%s)\\%s" constchar* 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;
}
class PdhMutex : public StackObj { private: static Semaphore _semaphore; public:
PdhMutex() {
_semaphore.wait();
}
~PdhMutex() {
_semaphore.signal();
}
};
Semaphore PdhMutex::_semaphore(1);
staticvoid 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. constint 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;
}
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 != NULL, "process_info is NULL!"); char* exePath = NULL;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, _pe32.th32ProcessID); if (hProcess != NULL) {
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;
}
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 = NULL; for (ULONG i = 0; i < table->NumEntries; ++i) { if (table->Table[i].InterfaceAndOperStatusFlags.FilterInterface) { continue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.