| /* |
| * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2022, 2024, IBM Corp. |
| * 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 "precompiled.hpp" |
| #include "jvm.h" |
| #include "libperfstat_aix.hpp" |
| #include "memory/allocation.inline.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "os_aix.inline.hpp" |
| #include "runtime/os.hpp" |
| #include "runtime/os_perf.hpp" |
| #include "runtime/vm_version.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| |
| #include <dirent.h> |
| #include <dlfcn.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <pthread.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/procfs.h> |
| #include <sys/resource.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| typedef struct { |
| u_longlong_t user; |
| u_longlong_t sys; |
| u_longlong_t idle; |
| u_longlong_t wait; |
| } cpu_tick_store_t; |
| |
| typedef struct { |
| double utime; |
| double stime; |
| } jvm_time_store_t; |
| |
| enum { |
| UNDETECTED, |
| UNDETECTABLE, |
| LINUX26_NPTL, |
| BAREMETAL |
| }; |
| |
| /** |
| * Get info for requested PID from /proc/<pid>/psinfo file |
| */ |
| static bool read_psinfo(const u_longlong_t& pid, psinfo_t& psinfo) { |
| static size_t BUF_LENGTH = 32 + sizeof(u_longlong_t); |
| |
| FILE* fp; |
| char buf[BUF_LENGTH]; |
| int len; |
| |
| jio_snprintf(buf, BUF_LENGTH, "/proc/%llu/psinfo", pid); |
| fp = fopen(buf, "r"); |
| |
| if (!fp) { |
| return false; |
| } |
| |
| len = fread(&psinfo, 1, sizeof(psinfo_t), fp); |
| fclose(fp); |
| return len == sizeof(psinfo_t); |
| } |
| |
| /** |
| * Get and set ticks for the specified lcpu |
| */ |
| static OSReturn get_lcpu_ticks(perfstat_id_t* lcpu_name, cpu_tick_store_t* pticks) { |
| perfstat_cpu_t lcpu_stats; |
| |
| if (!pticks) { |
| return OS_ERR; |
| } |
| |
| // populate cpu_stats |
| if (libperfstat::perfstat_cpu(lcpu_name, &lcpu_stats, sizeof(perfstat_cpu_t), 1) < 1) { |
| memset(pticks, 0, sizeof(cpu_tick_store_t)); |
| return OS_ERR; |
| } |
| |
| pticks->user = lcpu_stats.user; |
| pticks->sys = lcpu_stats.sys; |
| pticks->idle = lcpu_stats.idle; |
| pticks->wait = lcpu_stats.wait; |
| |
| return OS_OK; |
| } |
| |
| /** |
| * Return CPU load caused by the currently executing process (the jvm). |
| */ |
| static OSReturn get_jvm_load(double* jvm_uload, double* jvm_sload) { |
| static clock_t ticks_per_sec = sysconf(_SC_CLK_TCK); |
| static u_longlong_t last_timebase = 0; |
| |
| perfstat_process_t jvm_stats; |
| perfstat_id_t name_holder; |
| u_longlong_t timebase_diff; |
| |
| jio_snprintf(name_holder.name, IDENTIFIER_LENGTH, "%d", getpid()); |
| if (libperfstat::perfstat_process(&name_holder, &jvm_stats, sizeof(perfstat_process_t), 1) < 1) { |
| return OS_ERR; |
| } |
| |
| // Update timebase |
| timebase_diff = jvm_stats.last_timebase - last_timebase; |
| last_timebase = jvm_stats.last_timebase; |
| |
| if (jvm_uload) { |
| *jvm_uload = jvm_stats.ucpu_time / timebase_diff; |
| } |
| if (jvm_sload) { |
| *jvm_sload = jvm_stats.scpu_time / timebase_diff; |
| } |
| |
| return OS_OK; |
| } |
| |
| static void update_prev_time(jvm_time_store_t* from, jvm_time_store_t* to) { |
| if (from && to) { |
| memcpy(to, from, sizeof(jvm_time_store_t)); |
| } |
| } |
| |
| static void update_prev_ticks(cpu_tick_store_t* from, cpu_tick_store_t* to) { |
| if (from && to) { |
| memcpy(to, from, sizeof(cpu_tick_store_t)); |
| } |
| } |
| |
| /** |
| * Calculate the current system load from current ticks using previous ticks as a starting point. |
| */ |
| static void calculate_updated_load(cpu_tick_store_t* update, cpu_tick_store_t* prev, double* load) { |
| cpu_tick_store_t diff; |
| |
| if (update && prev && load) { |
| diff.user = update->user - prev->user; |
| diff.sys = update->sys - prev->sys; |
| diff.idle = update->idle - prev->idle; |
| diff.wait = update->wait - prev->wait; |
| |
| *load = 1.0 - diff.idle/(diff.sys + diff.user + diff.idle + diff.wait); |
| } |
| } |
| |
| /** |
| * Look up lcpu names for later re-use. |
| */ |
| static bool populate_lcpu_names(int ncpus, perfstat_id_t* lcpu_names) { |
| ResourceMark rm; |
| perfstat_cpu_t* all_lcpu_stats; |
| perfstat_cpu_t* lcpu_stats; |
| perfstat_id_t name_holder; |
| |
| assert(lcpu_names, "Names pointer null"); |
| |
| strncpy(name_holder.name, FIRST_CPU, IDENTIFIER_LENGTH); |
| |
| all_lcpu_stats = NEW_RESOURCE_ARRAY(perfstat_cpu_t, ncpus); |
| |
| // If perfstat_cpu does not return the expected number of names, signal error to caller |
| if (ncpus != libperfstat::perfstat_cpu(&name_holder, all_lcpu_stats, sizeof(perfstat_cpu_t), ncpus)) { |
| return false; |
| } |
| |
| for (int n = 0; n < ncpus; n++) { |
| strncpy(lcpu_names[n].name, all_lcpu_stats[n].name, IDENTIFIER_LENGTH); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Calculates the context switch rate. |
| * (Context Switches / Tick) * (Tick / s) = Context Switches per second |
| */ |
| static OSReturn perf_context_switch_rate(double* rate) { |
| static clock_t ticks_per_sec = sysconf(_SC_CLK_TCK); |
| |
| u_longlong_t ticks; |
| perfstat_cpu_total_t cpu_stats; |
| |
| if (libperfstat::perfstat_cpu_total(nullptr, &cpu_stats, sizeof(perfstat_cpu_total_t), 1) < 0) { |
| return OS_ERR; |
| } |
| |
| ticks = cpu_stats.user + cpu_stats.sys + cpu_stats.idle + cpu_stats.wait; |
| *rate = (cpu_stats.pswitch / ticks) * ticks_per_sec; |
| |
| return OS_OK; |
| } |
| |
| class CPUPerformanceInterface::CPUPerformance : public CHeapObj<mtInternal> { |
| private: |
| int _ncpus; |
| perfstat_id_t* _lcpu_names; |
| cpu_tick_store_t* _prev_ticks; |
| |
| public: |
| CPUPerformance(); |
| bool initialize(); |
| ~CPUPerformance(); |
| |
| 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* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad); |
| }; |
| |
| CPUPerformanceInterface::CPUPerformance::CPUPerformance(): |
| _ncpus(0), |
| _lcpu_names(nullptr), |
| _prev_ticks(nullptr) {} |
| |
| bool CPUPerformanceInterface::CPUPerformance::initialize() { |
| perfstat_cpu_total_t cpu_stats; |
| |
| if (libperfstat::perfstat_cpu_total(nullptr, &cpu_stats, sizeof(perfstat_cpu_total_t), 1) < 0) { |
| return false; |
| } |
| if (cpu_stats.ncpus <= 0) { |
| return false; |
| } |
| |
| _ncpus = cpu_stats.ncpus; |
| _lcpu_names = NEW_C_HEAP_ARRAY(perfstat_id_t, _ncpus, mtInternal); |
| |
| _prev_ticks = NEW_C_HEAP_ARRAY(cpu_tick_store_t, _ncpus, mtInternal); |
| // Set all prev-tick values to 0 |
| memset(_prev_ticks, 0, _ncpus*sizeof(cpu_tick_store_t)); |
| |
| if (!populate_lcpu_names(_ncpus, _lcpu_names)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| CPUPerformanceInterface::CPUPerformance::~CPUPerformance() { |
| if (_lcpu_names) { |
| FREE_C_HEAP_ARRAY(perfstat_id_t, _lcpu_names); |
| } |
| if (_prev_ticks) { |
| FREE_C_HEAP_ARRAY(cpu_tick_store_t, _prev_ticks); |
| } |
| } |
| |
| /** |
| * Get CPU load for all processes on specified logical CPU. |
| */ |
| int CPUPerformanceInterface::CPUPerformance::cpu_load(int lcpu_number, double* lcpu_load) { |
| cpu_tick_store_t ticks; |
| |
| assert(lcpu_load != nullptr, "null pointer passed to cpu_load"); |
| assert(lcpu_number < _ncpus, "Invalid lcpu passed to cpu_load"); |
| |
| if (get_lcpu_ticks(&_lcpu_names[lcpu_number], &ticks) == OS_ERR) { |
| *lcpu_load = -1.0; |
| return OS_ERR; |
| } |
| |
| calculate_updated_load(&ticks, &_prev_ticks[lcpu_number], lcpu_load); |
| update_prev_ticks(&ticks, &_prev_ticks[lcpu_number]); |
| |
| return OS_OK; |
| } |
| |
| /** |
| * Get CPU load for all processes on all CPUs. |
| */ |
| int CPUPerformanceInterface::CPUPerformance::cpu_load_total_process(double* total_load) { |
| cpu_tick_store_t total_ticks; |
| cpu_tick_store_t prev_total_ticks; |
| |
| assert(total_load != nullptr, "null pointer passed to cpu_load_total_process"); |
| |
| memset(&total_ticks, 0, sizeof(cpu_tick_store_t)); |
| memset(&prev_total_ticks, 0, sizeof(cpu_tick_store_t)); |
| |
| for (int lcpu = 0; lcpu < _ncpus; lcpu++) { |
| cpu_tick_store_t lcpu_ticks; |
| |
| if (get_lcpu_ticks(&_lcpu_names[lcpu], &lcpu_ticks) == OS_ERR) { |
| *total_load = -1.0; |
| return OS_ERR; |
| } |
| |
| total_ticks.user = lcpu_ticks.user; |
| total_ticks.sys = lcpu_ticks.sys; |
| total_ticks.idle = lcpu_ticks.idle; |
| total_ticks.wait = lcpu_ticks.wait; |
| |
| prev_total_ticks.user += _prev_ticks[lcpu].user; |
| prev_total_ticks.sys += _prev_ticks[lcpu].sys; |
| prev_total_ticks.idle += _prev_ticks[lcpu].idle; |
| prev_total_ticks.wait += _prev_ticks[lcpu].wait; |
| |
| update_prev_ticks(&lcpu_ticks, &_prev_ticks[lcpu]); |
| } |
| |
| calculate_updated_load(&total_ticks, &prev_total_ticks, total_load); |
| |
| return OS_OK; |
| } |
| |
| /** |
| * Get CPU load for all CPUs. |
| * |
| * Set values for: |
| * - pjvmUserLoad: CPU load due to jvm process in user mode. Jvm process assumed to be self process |
| * - pjvmKernelLoad: CPU load due to jvm process in kernel mode. Jvm process assumed to be self process |
| * - psystemTotalLoad: Total CPU load from all process on all logical CPUs |
| * |
| * Note: If any of the above loads cannot be calculated, this procedure returns OS_ERR and any load that could not be calculated is set to -1 |
| * |
| */ |
| int CPUPerformanceInterface::CPUPerformance::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) { |
| double u, k, t; |
| |
| int retval = OS_OK; |
| if (get_jvm_load(&u, &k) == OS_ERR || cpu_load_total_process(&t) == OS_ERR) { |
| retval = OS_ERR; |
| } |
| |
| if (pjvmUserLoad) { |
| *pjvmUserLoad = u; |
| } |
| if (pjvmKernelLoad) { |
| *pjvmKernelLoad = k; |
| } |
| if (psystemTotalLoad) { |
| *psystemTotalLoad = t; |
| } |
| |
| return retval; |
| } |
| |
| int CPUPerformanceInterface::CPUPerformance::context_switch_rate(double* rate) { |
| return perf_context_switch_rate(rate); |
| } |
| |
| 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::cpu_load_total_process(double* cpu_load) const { |
| return _impl->cpu_load_total_process(cpu_load); |
| } |
| |
| int CPUPerformanceInterface::cpu_loads_process(double* pjvmUserLoad, double* pjvmKernelLoad, double* psystemTotalLoad) const { |
| return _impl->cpu_loads_process(pjvmUserLoad, pjvmKernelLoad, psystemTotalLoad); |
| } |
| |
| int CPUPerformanceInterface::context_switch_rate(double* rate) const { |
| return _impl->context_switch_rate(rate); |
| } |
| |
| class SystemProcessInterface::SystemProcesses : public CHeapObj<mtInternal> { |
| private: |
| char* allocate_string(const char* str) const; |
| |
| public: |
| SystemProcesses(); |
| bool initialize(); |
| ~SystemProcesses(); |
| int system_processes(SystemProcess** system_processes, int* no_of_sys_processes) const; |
| }; |
| |
| SystemProcessInterface::SystemProcesses::SystemProcesses() { |
| } |
| |
| bool SystemProcessInterface::SystemProcesses::initialize() { |
| return true; |
| } |
| |
| SystemProcessInterface::SystemProcesses::~SystemProcesses() { |
| } |
| |
| char* SystemProcessInterface::SystemProcesses::allocate_string(const char* str) const { |
| if (str != nullptr) { |
| return os::strdup_check_oom(str, mtInternal); |
| } |
| return nullptr; |
| } |
| |
| int SystemProcessInterface::SystemProcesses::system_processes(SystemProcess** system_processes, int* nprocs) const { |
| ResourceMark rm; |
| perfstat_process_t* proc_stats; |
| SystemProcess* head; |
| perfstat_id_t name_holder; |
| int records_allocated = 0; |
| |
| assert(nprocs != nullptr, "system_processes counter pointers is null!"); |
| |
| head = nullptr; |
| *nprocs = 0; |
| strncpy(name_holder.name, "", IDENTIFIER_LENGTH); |
| |
| // calling perfstat_<subsystem>(null, null, _, 0) returns number of available records |
| *nprocs = libperfstat::perfstat_process(nullptr, nullptr, sizeof(perfstat_process_t), 0); |
| if(*nprocs < 1) { |
| // expect at least 1 process |
| return OS_ERR; |
| } |
| |
| records_allocated = *nprocs; |
| proc_stats = NEW_RESOURCE_ARRAY(perfstat_process_t, records_allocated); |
| |
| // populate stats && set the actual number of procs that have been populated |
| // should never be higher than requested, but may be lower due to process death |
| *nprocs = libperfstat::perfstat_process(&name_holder, proc_stats, sizeof(perfstat_process_t), records_allocated); |
| |
| for (int n = 0; n < *nprocs; n++) { |
| psinfo_t psinfo; |
| // Note: SystemProcess with free these in its dtor. |
| char* name = NEW_C_HEAP_ARRAY(char, IDENTIFIER_LENGTH, mtInternal); |
| char* exe_name = NEW_C_HEAP_ARRAY(char, PRFNSZ, mtInternal); |
| char* cmd_line = NEW_C_HEAP_ARRAY(char, PRARGSZ, mtInternal); |
| |
| strncpy(name, proc_stats[n].proc_name, IDENTIFIER_LENGTH); |
| |
| if (read_psinfo(proc_stats[n].pid, psinfo)) { |
| strncpy(exe_name, psinfo.pr_fname, PRFNSZ); |
| strncpy(cmd_line, psinfo.pr_psargs, PRARGSZ); |
| } |
| |
| // create a new SystemProcess with next pointing to current head. |
| SystemProcess* sp = new SystemProcess(proc_stats[n].pid, |
| name, |
| exe_name, |
| cmd_line, |
| head); |
| // update head. |
| head = sp; |
| } |
| |
| *system_processes = head; |
| 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) { |
| if (_cpu_info->cpu_name() != nullptr) { |
| const char* cpu_name = _cpu_info->cpu_name(); |
| FREE_C_HEAP_ARRAY(char, cpu_name); |
| _cpu_info->set_cpu_name(nullptr); |
| } |
| if (_cpu_info->cpu_description() != nullptr) { |
| const char* cpu_desc = _cpu_info->cpu_description(); |
| FREE_C_HEAP_ARRAY(char, cpu_desc); |
| _cpu_info->set_cpu_description(nullptr); |
| } |
| delete _cpu_info; |
| } |
| } |
| |
| int CPUInformationInterface::cpu_information(CPUInformation& cpu_info) { |
| if (_cpu_info == nullptr) { |
| return OS_ERR; |
| } |
| |
| cpu_info = *_cpu_info; // shallow copy assignment |
| return OS_OK; |
| } |
| |
| class NetworkPerformanceInterface::NetworkPerformance : public CHeapObj<mtInternal> { |
| NONCOPYABLE(NetworkPerformance); |
| |
| private: |
| char* allocate_string(const char* str) const; |
| |
| public: |
| NetworkPerformance(); |
| bool initialize(); |
| ~NetworkPerformance(); |
| int network_utilization(NetworkInterface** network_interfaces) const; |
| }; |
| |
| NetworkPerformanceInterface::NetworkPerformance::NetworkPerformance() {} |
| |
| bool NetworkPerformanceInterface::NetworkPerformance::initialize() { |
| return true; |
| } |
| |
| NetworkPerformanceInterface::NetworkPerformance::~NetworkPerformance() {} |
| |
| int NetworkPerformanceInterface::NetworkPerformance::network_utilization(NetworkInterface** network_interfaces) const { |
| int n_records = 0; |
| perfstat_netinterface_t* net_stats; |
| perfstat_id_t name_holder; |
| int records_allocated = 0; |
| |
| assert(network_interfaces != nullptr, "network_interfaces is null"); |
| |
| *network_interfaces = nullptr; |
| strncpy(name_holder.name , FIRST_NETINTERFACE, IDENTIFIER_LENGTH); |
| |
| // calling perfstat_<subsystem>(null, null, _, 0) returns number of available records |
| if ((n_records = libperfstat::perfstat_netinterface(nullptr, nullptr, sizeof(perfstat_netinterface_t), 0)) < 0) { |
| return OS_ERR; |
| } |
| |
| records_allocated = n_records; |
| net_stats = NEW_C_HEAP_ARRAY(perfstat_netinterface_t, records_allocated, mtInternal); |
| |
| n_records = libperfstat::perfstat_netinterface(&name_holder, net_stats, sizeof(perfstat_netinterface_t), n_records); |
| |
| // check for error |
| if (n_records < 0) { |
| FREE_C_HEAP_ARRAY(perfstat_netinterface_t, net_stats); |
| return OS_ERR; |
| } |
| |
| for (int i = 0; i < n_records; i++) { |
| // Create new Network interface *with current head as next node* |
| // Note: NetworkInterface makes copies of these string values into RA memory |
| // this means: |
| // (1) we are free to clean our values upon exiting this proc |
| // (2) we avoid using RA-alloced memory here (ie. do not use NEW_RESOURCE_ARRAY) |
| NetworkInterface* new_interface = new NetworkInterface(net_stats[i].name, |
| net_stats[i].ibytes, |
| net_stats[i].obytes, |
| *network_interfaces); |
| *network_interfaces = new_interface; |
| } |
| |
| FREE_C_HEAP_ARRAY(perfstat_netinterface_t, net_stats); |
| 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); |
| } |