| /* | |
| * Copyright (c) 2011, 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. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include <stdio.h> | |
| #include <stdint.h> | |
| #include <stdarg.h> | |
| #include <unistd.h> | |
| #include <errno.h> | |
| #include <string.h> | |
| #include <sys/resource.h> | |
| #include <sys/types.h> | |
| #include <dirent.h> | |
| #include <stdlib.h> | |
| #include <dlfcn.h> | |
| #include <pthread.h> | |
| #include <inttypes.h> | |
| #include "management_ext.h" | |
| #include "com_sun_management_internal_OperatingSystemImpl.h" | |
| #include <assert.h> | |
| struct ticks { | |
| uint64_t used; | |
| uint64_t usedKernel; | |
| uint64_t total; | |
| }; | |
| typedef struct ticks ticks; | |
| typedef enum { | |
| CPU_LOAD_VM_ONLY, | |
| CPU_LOAD_GLOBAL, | |
| } CpuLoadTarget; | |
| static struct perfbuf { | |
| int nProcs; | |
| ticks jvmTicks; | |
| ticks cpuTicks; | |
| ticks *cpus; | |
| } counters; | |
| #define DEC_64 "%"SCNd64 | |
| #define NS_PER_SEC 1000000000 | |
| static int next_line(FILE *f) { | |
| int c; | |
| do { | |
| c = fgetc(f); | |
| } while (c != '\n' && c != EOF); | |
| return c; | |
| } | |
| /** | |
| * Return the total number of ticks since the system was booted. | |
| * If the usedTicks parameter is not NULL, it will be filled with | |
| * the number of ticks spent on actual processes (user, system or | |
| * nice processes) since system boot. Note that this is the total number | |
| * of "executed" ticks on _all_ CPU:s, that is on a n-way system it is | |
| * n times the number of ticks that has passed in clock time. | |
| * | |
| * Returns a negative value if the reading of the ticks failed. | |
| */ | |
| static int get_totalticks(int which, ticks *pticks) { | |
| FILE *fh; | |
| uint64_t userTicks, niceTicks, systemTicks, idleTicks; | |
| uint64_t iowTicks = 0, irqTicks = 0, sirqTicks= 0; | |
| int n; | |
| if((fh = fopen("/proc/stat", "r")) == NULL) { | |
| return -1; | |
| } | |
| n = fscanf(fh, "cpu " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " | |
| DEC_64 " " DEC_64, | |
| &userTicks, &niceTicks, &systemTicks, &idleTicks, | |
| &iowTicks, &irqTicks, &sirqTicks); | |
| // Move to next line | |
| if (next_line(fh) == EOF) { | |
| fclose(fh); | |
| return -2; | |
| } | |
| //find the line for requested cpu faster to just iterate linefeeds? | |
| if (which != -1) { | |
| int i; | |
| for (i = 0; i < which; i++) { | |
| if (fscanf(fh, "cpu%*d " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " | |
| DEC_64 " " DEC_64 " " DEC_64, | |
| &userTicks, &niceTicks, &systemTicks, &idleTicks, | |
| &iowTicks, &irqTicks, &sirqTicks) < 4) { | |
| fclose(fh); | |
| return -2; | |
| } | |
| if (next_line(fh) == EOF) { | |
| fclose(fh); | |
| return -2; | |
| } | |
| } | |
| n = fscanf(fh, "cpu%*d " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " | |
| DEC_64 " " DEC_64 " " DEC_64 "\n", | |
| &userTicks, &niceTicks, &systemTicks, &idleTicks, | |
| &iowTicks, &irqTicks, &sirqTicks); | |
| } | |
| fclose(fh); | |
| if (n < 4) { | |
| return -2; | |
| } | |
| pticks->used = userTicks + niceTicks; | |
| pticks->usedKernel = systemTicks + irqTicks + sirqTicks; | |
| pticks->total = userTicks + niceTicks + systemTicks + idleTicks + | |
| iowTicks + irqTicks + sirqTicks; | |
| return 0; | |
| } | |
| static int vread_statdata(const char *procfile, const char *fmt, va_list args) { | |
| FILE *f; | |
| int n; | |
| char buf[2048]; | |
| if ((f = fopen(procfile, "r")) == NULL) { | |
| return -1; | |
| } | |
| if ((n = fread(buf, 1, sizeof(buf), f)) != -1) { | |
| char *tmp; | |
| buf[n-1] = '\0'; | |
| /** skip through pid and exec name. the exec name _could be wacky_ (renamed) and | |
| * make scanf go mupp. | |
| */ | |
| if ((tmp = strrchr(buf, ')')) != NULL) { | |
| // skip the ')' and the following space but check that the buffer is long enough | |
| tmp += 2; | |
| if (tmp < buf + n) { | |
| n = vsscanf(tmp, fmt, args); | |
| } | |
| } | |
| } | |
| fclose(f); | |
| return n; | |
| } | |
| static int read_statdata(const char *procfile, const char *fmt, ...) { | |
| int n; | |
| va_list args; | |
| va_start(args, fmt); | |
| n = vread_statdata(procfile, fmt, args); | |
| va_end(args); | |
| return n; | |
| } | |
| /** read user and system ticks from a named procfile, assumed to be in 'stat' format then. */ | |
| static int read_ticks(const char *procfile, uint64_t *userTicks, uint64_t *systemTicks) { | |
| return read_statdata(procfile, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u "DEC_64" "DEC_64, | |
| userTicks, systemTicks | |
| ); | |
| } | |
| /** | |
| * Return the number of ticks spent in any of the processes belonging | |
| * to the JVM on any CPU. | |
| */ | |
| static int get_jvmticks(ticks *pticks) { | |
| uint64_t userTicks; | |
| uint64_t systemTicks; | |
| if (read_ticks("/proc/self/stat", &userTicks, &systemTicks) != 2) { | |
| return -1; | |
| } | |
| // get the total | |
| if (get_totalticks(-1, pticks) < 0) { | |
| return -1; | |
| } | |
| pticks->used = userTicks; | |
| pticks->usedKernel = systemTicks; | |
| return 0; | |
| } | |
| /** | |
| * This method must be called first, before any data can be gathererd. | |
| */ | |
| int perfInit() { | |
| static int initialized = 0; | |
| if (!initialized) { | |
| int i; | |
| // We need to allocate counters for all CPUs, including ones that | |
| // are currently offline as they could be turned online later. | |
| int n = sysconf(_SC_NPROCESSORS_CONF); | |
| if (n <= 0) { | |
| n = 1; | |
| } | |
| counters.cpus = calloc(n,sizeof(ticks)); | |
| counters.nProcs = n; | |
| if (counters.cpus != NULL) { | |
| // For the CPU load | |
| get_totalticks(-1, &counters.cpuTicks); | |
| for (i = 0; i < n; i++) { | |
| get_totalticks(i, &counters.cpus[i]); | |
| } | |
| // For JVM load | |
| get_jvmticks(&counters.jvmTicks); | |
| initialized = 1; | |
| } | |
| } | |
| return initialized ? 0 : -1; | |
| } | |
| #define MAX(a,b) (a>b?a:b) | |
| #define MIN(a,b) (a<b?a:b) | |
| static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; | |
| /** | |
| * Return the load of the CPU as a double. 1.0 means the CPU process uses all | |
| * available time for user or system processes, 0.0 means the CPU uses all time | |
| * being idle. | |
| * | |
| * Returns a negative value if there is a problem in determining the CPU load. | |
| */ | |
| static double get_cpuload_internal(int which, double *pkernelLoad, CpuLoadTarget target) { | |
| uint64_t udiff, kdiff, tdiff; | |
| ticks *pticks, tmp; | |
| double user_load = -1.0; | |
| int failed = 0; | |
| *pkernelLoad = 0.0; | |
| pthread_mutex_lock(&lock); | |
| if (perfInit() == 0) { | |
| if (target == CPU_LOAD_VM_ONLY) { | |
| pticks = &counters.jvmTicks; | |
| } else if (which == -1) { | |
| pticks = &counters.cpuTicks; | |
| } else { | |
| pticks = &counters.cpus[which]; | |
| } | |
| tmp = *pticks; | |
| if (target == CPU_LOAD_VM_ONLY) { | |
| if (get_jvmticks(pticks) != 0) { | |
| failed = 1; | |
| } | |
| } else if (get_totalticks(which, pticks) < 0) { | |
| failed = 1; | |
| } | |
| if (!failed) { | |
| assert(pticks->usedKernel >= tmp.usedKernel); | |
| kdiff = pticks->usedKernel - tmp.usedKernel; | |
| tdiff = pticks->total - tmp.total; | |
| udiff = pticks->used - tmp.used; | |
| if (tdiff == 0) { | |
| user_load = 0; | |
| } else { | |
| if (tdiff < (udiff + kdiff)) { | |
| tdiff = udiff + kdiff; | |
| } | |
| *pkernelLoad = (kdiff / (double)tdiff); | |
| // BUG9044876, normalize return values to sane values | |
| *pkernelLoad = MAX(*pkernelLoad, 0.0); | |
| *pkernelLoad = MIN(*pkernelLoad, 1.0); | |
| user_load = (udiff / (double)tdiff); | |
| user_load = MAX(user_load, 0.0); | |
| user_load = MIN(user_load, 1.0); | |
| } | |
| } | |
| } | |
| pthread_mutex_unlock(&lock); | |
| return user_load; | |
| } | |
| double get_cpu_load(int which) { | |
| double u, s; | |
| u = get_cpuload_internal(which, &s, CPU_LOAD_GLOBAL); | |
| if (u < 0) { | |
| return -1.0; | |
| } | |
| // Cap total systemload to 1.0 | |
| return MIN((u + s), 1.0); | |
| } | |
| double get_process_load() { | |
| double u, s; | |
| u = get_cpuload_internal(-1, &s, CPU_LOAD_VM_ONLY); | |
| if (u < 0) { | |
| return -1.0; | |
| } | |
| return u + s; | |
| } | |
| static long read_vmem_usage(const char *procfile, unsigned long *vsize) { | |
| int n = read_statdata(procfile, "%*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d %*d %*u %*u %*d %lu %*[^\n]\n", vsize); | |
| if (n != 1) { | |
| return -1; | |
| } else { | |
| return *vsize; | |
| } | |
| } | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getCommittedVirtualMemorySize0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| unsigned long vsize; | |
| if (read_vmem_usage("/proc/self/stat", &vsize) == -1) { | |
| throw_internal_error(env, "Unable to get virtual memory usage"); | |
| } | |
| return (jlong) vsize; | |
| } | |
| JNIEXPORT jdouble JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getCpuLoad0 | |
| (JNIEnv *env, jobject dummy) | |
| { | |
| if (perfInit() == 0) { | |
| return get_cpu_load(-1); | |
| } else { | |
| return -1.0; | |
| } | |
| } | |
| JNIEXPORT jdouble JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getProcessCpuLoad0 | |
| (JNIEnv *env, jobject dummy) | |
| { | |
| if (perfInit() == 0) { | |
| return get_process_load(); | |
| } else { | |
| return -1.0; | |
| } | |
| } | |
| JNIEXPORT jdouble JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getSingleCpuLoad0 | |
| (JNIEnv *env, jobject mbean, jint cpu_number) | |
| { | |
| if (perfInit() == 0 && cpu_number >= 0 && cpu_number < counters.nProcs) { | |
| return get_cpu_load(cpu_number); | |
| } else { | |
| return -1.0; | |
| } | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getHostConfiguredCpuCount0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| if (perfInit() == 0) { | |
| return counters.nProcs; | |
| } else { | |
| return -1; | |
| } | |
| } | |
| // Return the host cpu ticks since boot in nanoseconds | |
| JNIEXPORT jlong JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getHostTotalCpuTicks0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| if (perfInit() == 0) { | |
| if (get_totalticks(-1, &counters.cpuTicks) < 0) { | |
| return -1; | |
| } else { | |
| long ticks_per_sec = sysconf(_SC_CLK_TCK); | |
| jlong result = (jlong)counters.cpuTicks.total; | |
| if (ticks_per_sec <= NS_PER_SEC) { | |
| long scale_factor = NS_PER_SEC/ticks_per_sec; | |
| result = result * scale_factor; | |
| } else { | |
| long scale_factor = ticks_per_sec/NS_PER_SEC; | |
| result = result / scale_factor; | |
| } | |
| return result; | |
| } | |
| } else { | |
| return -1; | |
| } | |
| } | |
| JNIEXPORT jint JNICALL | |
| Java_com_sun_management_internal_OperatingSystemImpl_getHostOnlineCpuCount0 | |
| (JNIEnv *env, jobject mbean) | |
| { | |
| int n = sysconf(_SC_NPROCESSORS_ONLN); | |
| if (n <= 0) { | |
| n = 1; | |
| } | |
| return n; | |
| } | |