From 147889cef7be31ce3c9e6cea769dd2898a2fbc7a Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Fri, 28 Aug 2026 08:47:45 +0200 Subject: [PATCH 1/2] Add loadaverage to vitals. --- src/hotspot/os/linux/vitals_linux.cpp | 9 +++++- .../os/linux/vitals_linux_oswrapper.cpp | 21 ++++++++++-- .../os/linux/vitals_linux_oswrapper.hpp | 1 + src/hotspot/share/vitals/vitals.cpp | 32 ++++++++++++++++++- src/hotspot/share/vitals/vitals_internals.hpp | 13 ++++++++ 5 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/hotspot/os/linux/vitals_linux.cpp b/src/hotspot/os/linux/vitals_linux.cpp index bb0c5b318770..eebe9ed386cf 100644 --- a/src/hotspot/os/linux/vitals_linux.cpp +++ b/src/hotspot/os/linux/vitals_linux.cpp @@ -106,6 +106,8 @@ static Column* g_col_system_num_threads = nullptr; static Column* g_col_system_num_procs_running = nullptr; static Column* g_col_system_num_procs_blocked = nullptr; +static Column* g_col_system_load_avg = nullptr; + static bool g_show_cgroup_info = false; static Column* g_col_system_cgrp_limit_in_bytes = nullptr; static Column* g_col_system_cgrp_soft_limit_in_bytes = nullptr; @@ -176,10 +178,13 @@ bool platform_columns_initialize() { define_column(system_cat, nullptr, "t", "Number of threads", true); g_col_system_num_procs_running = - define_column(system_cat, nullptr, "tr", "Number of threads running", true); + define_column(system_cat, nullptr, "tr", "Number of running and runnable threads", true); g_col_system_num_procs_blocked = define_column(system_cat, nullptr, "tb", "Number of threads blocked on disk IO", true); + g_col_system_load_avg = + define_column(system_cat, nullptr, "la", "Load average in percent", true, MAX); + g_col_system_cpu_user = define_column(system_cat, "cpu", "us", "CPU user time [host]", true); g_col_system_cpu_system = @@ -287,6 +292,8 @@ void sample_platform_values(Sample* sample) { set_value_in_sample(g_col_system_num_procs_running, sample, OSWrapper::syst_tr()); set_value_in_sample(g_col_system_num_procs_blocked, sample, OSWrapper::syst_tb()); + set_value_in_sample(g_col_system_load_avg, sample, OSWrapper::syst_ldavg()); + // cgroups business if (g_show_cgroup_info) { set_value_in_sample(g_col_system_cgrp_usage_in_bytes, sample, OSWrapper::syst_cgro_usg()); diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp index a3d997dd6a45..f9b577e256fc 100644 --- a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp +++ b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp @@ -35,7 +35,6 @@ #include #include - #define LOG_HERE_F(msg, ...) { printf("[%d] ", (int)::getpid()); ::printf(msg, __VA_ARGS__); printf("\n"); fflush(stdout); } #define LOG_HERE(msg) { printf("[%d] ", (int)::getpid()); ::printf("%s", msg); printf("\n"); fflush(stdout); } @@ -371,6 +370,7 @@ const char* CGroups::_file_lim = nullptr; const char* CGroups::_file_limsw = nullptr; const char* CGroups::_file_slim = nullptr; const char* CGroups::_file_kusg = nullptr; +double proc_scale_factor = 0.0; void OSWrapper::update_if_needed() { @@ -394,6 +394,7 @@ ALL_VALUES_DO(RESETVAL) if (first_call) { log_trace(vitals)("Read /proc/meminfo: \n%s", bf.text()); + proc_scale_factor = 100.0 / MAX2(1, os::processor_count()); } // All values in /proc/meminfo are in KB @@ -436,7 +437,7 @@ ALL_VALUES_DO(RESETVAL) _syst_cpu_st = values.steal; _syst_cpu_gu = values.guest + values.guest_nice; - // procs_running: this is actually number of threads running + // procs_running: this is running and runnable threads. // procs_blocked: number of threads blocked on real disk IO // See https://utcc.utoronto.ca/~cks/space/blog/linux/ProcessStatesAndProcStat // and https://lore.kernel.org/lkml/12601530441257@xenotime.net/#t @@ -556,6 +557,22 @@ ALL_VALUES_DO(RESETVAL) } #endif // __GLIBC__ + if (bf.read("/proc/loadavg")) { + float l1, l5, l15; + if (sscanf(bf.text(), "%f %f %f", &l1, &l5, &l15) == 3) { + // Convert to relative percentage-based loads, where 100 percent + // means the number of runnable threads equals the number of CPUs. + value_t rl1 = (value_t) MAX2(0.0, MIN2(65535.0, proc_scale_factor * l1)); + value_t rl5 = (value_t) MAX2(0.0, MIN2(65535.0, proc_scale_factor * l5)); + value_t rl15 = (value_t) MAX2(0.0, MIN2(65535.0, proc_scale_factor * l15)); + // We put the three values into one, since we want to display + // the longer averaged one in table with coarser resolution. + _syst_ldavg = (rl1 << 32) | (rl5 << 16) | rl15; + } else { + _syst_ldavg = INVALID_VALUE; + } + } + first_call = false; } diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.hpp b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp index f58813dda0e5..3eb2bf3e8e0c 100644 --- a/src/hotspot/os/linux/vitals_linux_oswrapper.hpp +++ b/src/hotspot/os/linux/vitals_linux_oswrapper.hpp @@ -48,6 +48,7 @@ class OSWrapper { f(syst_t) \ f(syst_tr) \ f(syst_tb) \ + f(syst_ldavg) \ f(syst_cpu_us) \ f(syst_cpu_sy) \ f(syst_cpu_id) \ diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp index b326b5338eca..4e2018fce5d6 100644 --- a/src/hotspot/share/vitals/vitals.cpp +++ b/src/hotspot/share/vitals/vitals.cpp @@ -551,13 +551,17 @@ int Column::do_print(outputStream* st, value_t value, value_t last_value, } } else { if (pi->raw) { - return printf_helper(st, UINT64_FORMAT, value); + return do_print_raw0(st, value); } else { return do_print0(st, value, last_value, last_value_age, pi); } } } +int Column::do_print_raw0(outputStream* st, value_t value) const { + return printf_helper(st, UINT64_FORMAT, value); +} + int PlainValueColumn::do_print0(outputStream* st, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const { @@ -591,6 +595,32 @@ int DeltaMemorySizeColumn::do_print0(outputStream* st, value_t value, return 0; } +int LoadAverageColumn::do_print0(outputStream* st, value_t value, + value_t last_value, int last_value_age, const print_info_t* pi) const { + if (value != INVALID_VALUE) { + value_t load_average; + + // Use the minute resolution until an age of 2.5 minutes + // and 5 minute resulution until an age of 7.5 minutes. + // The extremes use last_value_age, so they use the one minute average. + if (last_value_age <= 150) { + load_average = value >> 32; + } else if (last_value_age < 450) { + load_average = (value >> 16) & 0xffff; + } else { + load_average = value & 0xffff; + } + + return printf_helper(st, UINT64_FORMAT, load_average); + } + + return 0; +} + +int LoadAverageColumn::do_print_raw0(outputStream* st, value_t value) const { + return do_print0(st, value, INVALID_VALUE, 0, nullptr); +} + ////////////// sample printing /////////////////////////// // Print one sample. diff --git a/src/hotspot/share/vitals/vitals_internals.hpp b/src/hotspot/share/vitals/vitals_internals.hpp index 4314318575d3..df506b2f630e 100644 --- a/src/hotspot/share/vitals/vitals_internals.hpp +++ b/src/hotspot/share/vitals/vitals_internals.hpp @@ -93,6 +93,8 @@ namespace sapmachine_vitals { // output stream can be nullptr; in that case, method shall return number of characters it would have printed. virtual int do_print0(outputStream* os, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const = 0; + // Can be overridden by the implementation. By default writes the 64 bit value. + virtual int do_print_raw0(outputStream* os, value_t value) const; public: @@ -171,6 +173,17 @@ namespace sapmachine_vitals { {} }; + // Special column which can handle the 3 load averages in the raw value. + class LoadAverageColumn: public Column { + int do_print0(outputStream* os, value_t value, value_t last_value, + int last_value_age, const print_info_t* pi) const; + int do_print_raw0(outputStream* os, value_t value) const; + public: + LoadAverageColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) + {} + }; + ////// Legend: handles the legend class Legend: public CHeapObj { From c77ee49bb7495940f70cc68cf454c995dcba16af Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Fri, 28 Aug 2026 17:14:49 +0200 Subject: [PATCH 2/2] Included review suggestions. --- src/hotspot/os/linux/vitals_linux_oswrapper.cpp | 6 ++++++ src/hotspot/share/vitals/vitals.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp index f9b577e256fc..840a4dd1f72d 100644 --- a/src/hotspot/os/linux/vitals_linux_oswrapper.cpp +++ b/src/hotspot/os/linux/vitals_linux_oswrapper.cpp @@ -570,6 +570,12 @@ ALL_VALUES_DO(RESETVAL) _syst_ldavg = (rl1 << 32) | (rl5 << 16) | rl15; } else { _syst_ldavg = INVALID_VALUE; + static bool traced = false; + + if (!traced) { + log_trace(vitals)("Could not parse /proc/loadavg: \n%s", bf.text()); + traced = true; + } } } diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp index 4e2018fce5d6..67f0f1e7799d 100644 --- a/src/hotspot/share/vitals/vitals.cpp +++ b/src/hotspot/share/vitals/vitals.cpp @@ -601,7 +601,7 @@ int LoadAverageColumn::do_print0(outputStream* st, value_t value, value_t load_average; // Use the minute resolution until an age of 2.5 minutes - // and 5 minute resulution until an age of 7.5 minutes. + // and 5 minute resolution until an age of 7.5 minutes. // The extremes use last_value_age, so they use the one minute average. if (last_value_age <= 150) { load_average = value >> 32;