| /* | |
| * Copyright (c) 2012, 2025, 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 "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) { | |
| const size_t BUF_LENGTH = 32 + sizeof(u_longlong_t); | |
| FILE* fp; | |
| char buf[BUF_LENGTH]; | |
| size_t 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 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) { | |
| 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) * os::Posix::clock_tics_per_second(); | |
| 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); | |
| } |