From f42b72cdde590d2611af7af720899f62b07c0919 Mon Sep 17 00:00:00 2001 From: Ralf Schmelter Date: Fri, 7 Jul 2023 08:18:57 +0200 Subject: [PATCH] Store vitals samples at extremas for some sampled values --- src/hotspot/os/linux/vitals_linux.cpp | 6 +- src/hotspot/os/windows/vitals_windows.cpp | 4 +- src/hotspot/share/runtime/globals.hpp | 4 + src/hotspot/share/vitals/vitals.cpp | 91 ++++++++++++++----- src/hotspot/share/vitals/vitals_internals.hpp | 36 +++++--- 5 files changed, 102 insertions(+), 39 deletions(-) diff --git a/src/hotspot/os/linux/vitals_linux.cpp b/src/hotspot/os/linux/vitals_linux.cpp index 9d5100e16ed..b38e1bc623f 100644 --- a/src/hotspot/os/linux/vitals_linux.cpp +++ b/src/hotspot/os/linux/vitals_linux.cpp @@ -159,7 +159,7 @@ bool platform_columns_initialize() { // syst-avail depends on kernel version. g_show_system_memavail = OSWrapper::syst_avail() != INVALID_VALUE; g_col_system_memavail = - define_column(system_cat, NULL, "avail", "Memory available without swapping [host] [krn]", g_show_system_memavail); + define_column(system_cat, NULL, "avail", "Memory available without swapping [host] [krn]", g_show_system_memavail, MIN); g_col_system_memcommitted = define_column(system_cat, NULL, "comm", "Committed memory [host]", true); g_col_system_memcommitted_ratio = @@ -197,9 +197,9 @@ bool platform_columns_initialize() { // (which should come out as the same, but you never know g_show_cgroup_info = OSContainer::is_containerized() || (OSWrapper::syst_cgro_lim() != INVALID_VALUE || OSWrapper::syst_cgro_limsw() != INVALID_VALUE); g_col_system_cgrp_limit_in_bytes = - define_column(system_cat, "cgroup", "lim", "cgroup memory limit [cgrp]", g_show_cgroup_info); + define_column(system_cat, "cgroup", "lim", "cgroup memory limit [cgrp]", g_show_cgroup_info, MIN); g_col_system_cgrp_soft_limit_in_bytes = - define_column(system_cat, "cgroup", "slim", "cgroup memory soft limit [cgrp]", g_show_cgroup_info); + define_column(system_cat, "cgroup", "slim", "cgroup memory soft limit [cgrp]", g_show_cgroup_info, MIN); g_col_system_cgrp_usage_in_bytes = define_column(system_cat, "cgroup", "usg", "cgroup memory usage [cgrp]", g_show_cgroup_info); g_col_system_cgrp_kmem_usage_in_bytes = diff --git a/src/hotspot/os/windows/vitals_windows.cpp b/src/hotspot/os/windows/vitals_windows.cpp index 5305855281f..dbf083bb708 100644 --- a/src/hotspot/os/windows/vitals_windows.cpp +++ b/src/hotspot/os/windows/vitals_windows.cpp @@ -42,11 +42,11 @@ static Column* g_col_process_commit_charge = NULL; bool platform_columns_initialize() { g_col_system_memoryload = - define_column("system", NULL, "mload", "Approximate percentage of physical memory that is in use.", true); + define_column("system", NULL, "mload", "Approximate percentage of physical memory that is in use.", true, MAX); // MEMORYSTATUSEX ullAvailPhys g_col_system_avail_phys = - define_column("system", NULL, "avail-phys", "Amount of physical memory currently available.", true); + define_column("system", NULL, "avail-phys", "Amount of physical memory currently available.", true, MIN); // PROCESS_MEMORY_COUNTERS_EX WorkingSetSize g_col_process_working_set_size = define_column("system", NULL, "wset", "Working set size", true); diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index a178b3b7dea..22e82776c35 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -522,6 +522,10 @@ const int ObjectAlignmentInBytes = 8; product(bool, PrintVitalsAtExit, false, \ "Prints vitals at VM exit to tty.") \ \ + product(bool, StorePeakSamples, true, \ + "If enabled we store the samples for peak values of selected " \ + "types.") \ + \ product(ccstr, VitalsFile, NULL, \ "When DumpVitalsAtExit is set, the file name prefix for the " \ "output files (default is sapmachine_vitals_).") \ diff --git a/src/hotspot/share/vitals/vitals.cpp b/src/hotspot/share/vitals/vitals.cpp index 3858f3d2dd4..999e17b30a6 100644 --- a/src/hotspot/share/vitals/vitals.cpp +++ b/src/hotspot/share/vitals/vitals.cpp @@ -255,7 +255,7 @@ Legend::Legend() : _last_added_cat(NULL) {} void Legend::add_column_info(const char* const category, const char* const header, const char* const name, const char* const description) { // Print category label if this column opens a new category - if (_last_added_cat != category) { + if ((_last_added_cat == NULL) || (::strcmp(_last_added_cat, category) != 0)) { print_text_with_dashes(&_legend, category, 30); _legend.cr(); } @@ -317,7 +317,7 @@ void ColumnList::add_column(Column* c) { //////////////////// -static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { +static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width) { assert(pi->csv == false, "Not in csv mode"); ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN); @@ -336,7 +336,7 @@ static void print_category_line(outputStream* st, const ColumnWidths* widths, co } width = 0; } - width += widths->at(c->index()); + width += widths->at(c->index()) + add_width; width += 1; // divider between columns last_category_text = c->category(); c = c->next(); @@ -345,7 +345,7 @@ static void print_category_line(outputStream* st, const ColumnWidths* widths, co st->cr(); } -static void print_header_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { +static void print_header_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width) { assert(pi->csv == false, "Not in csv mode"); ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN); @@ -369,7 +369,7 @@ static void print_header_line(outputStream* st, const ColumnWidths* widths, cons } width = 0; } - width += widths->at(c->index()); + width += widths->at(c->index()) + add_width; width += 1; // divider between columns last_header_text = c->header(); c = c->next(); @@ -380,7 +380,7 @@ static void print_header_line(outputStream* st, const ColumnWidths* widths, cons st->cr(); } -static void print_column_names(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { +static void print_column_names(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width) { // Leave space for timestamp column if (pi->csv == false) { @@ -393,7 +393,7 @@ static void print_column_names(outputStream* st, const ColumnWidths* widths, con const Column* previous = NULL; while (c != NULL) { if (pi->csv == false) { - st->print("%-*s ", widths->at(c->index()), c->name()); + st->print("%-*s ", widths->at(c->index()) + add_width, c->name()); } else { // csv mode // csv: use comma as delimiter, don't pad, and precede name with category/header // (limited to 4 chars). @@ -487,11 +487,12 @@ static int print_memory_size(outputStream* st, size_t byte_size, size_t scale) ///////// class Column and childs /////////// -Column::Column(const char* category, const char* header, const char* name, const char* description) +Column::Column(const char* category, const char* header, const char* name, const char* description, Extremum extremum) : _category(category), _header(header), // may be NULL _name(name), _description(description), + _extremum(extremum), _next(NULL), _idx(-1), _idx_cat(-1), _idx_hdr(-1) {} @@ -578,7 +579,7 @@ int DeltaMemorySizeColumn::do_print0(outputStream* st, value_t value, // Print one sample. static void print_one_sample(outputStream* st, const Sample* sample, - const Sample* last_sample, const ColumnWidths* widths, const print_info_t* pi) { + const Sample* last_sample, const ColumnWidths* widths, const print_info_t* pi, int marked_index = -1, char mark = '*') { // Print timestamp and divider if (pi->csv) { @@ -607,6 +608,9 @@ static void print_one_sample(outputStream* st, const Sample* sample, } const int min_width = widths->at(idx); c->print_value(st, v, v2, age, min_width, pi); + if (marked_index >= 0) { + st->put(marked_index == idx ? mark : ' '); + } st->put(pi->csv ? ',' : ' '); c = c->next(); } @@ -754,7 +758,7 @@ class PrintSamplesClosure : public SampleTable::Closure { // sampleTables is a combination of two tables: a short term table and a long term table. // It takes care to feed new samples into these tables at the appropriate intervals. -class SampleTables: public CHeapObj { +class SampleTables : public CHeapObj { // Short term table: cover one hour, one sample per VitalsSampleInterval (default 10 seconds) static const int short_term_span_seconds = 3600; @@ -763,11 +767,12 @@ class SampleTables: public CHeapObj { static const int long_term_span_seconds = short_term_span_seconds * 24 * 14; static const int long_term_sample_interval = short_term_span_seconds; - static int short_term_tablesize() { return (short_term_span_seconds / VitalsSampleInterval) + 1; } + static int short_term_tablesize() { return (short_term_span_seconds / VitalsSampleInterval) + 1; } static const int long_term_tablesize = (long_term_span_seconds / long_term_sample_interval) + 1; SampleTable _short_term_table; SampleTable _long_term_table; + Sample* _extremum_samples; int _count; @@ -776,7 +781,7 @@ class SampleTables: public CHeapObj { char _temp_buffer[196 * K]; static void print_table(const SampleTable* table, outputStream* st, - const ColumnWidths* widths, const print_info_t* pi) { + const ColumnWidths* widths, const print_info_t* pi) { if (table->is_empty()) { st->print_cr("(no samples)"); return; @@ -785,12 +790,12 @@ class SampleTables: public CHeapObj { table->walk_table_locked(&prclos, !pi->reverse_ordering); } - static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) { + static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width = 0) { if (pi->csv == false) { - print_category_line(st, widths, pi); - print_header_line(st, widths, pi); + print_category_line(st, widths, pi, add_width); + print_header_line(st, widths, pi, add_width); } - print_column_names(st, widths, pi); + print_column_names(st, widths, pi, add_width); } // Helper, print a time span given in seconds- @@ -800,11 +805,14 @@ class SampleTables: public CHeapObj { const int days = secs / (60 * 60 * 24); if (days > 1) { st->print_cr("Last %d days:", days); - } else if (hrs > 1) { + } + else if (hrs > 1) { st->print_cr("Last %d hours:", hrs); - } else if (mins > 1) { + } + else if (mins > 1) { st->print_cr("Last %d minutes:", mins); - } else { + } + else { st->print_cr("Last %d seconds:", secs); } } @@ -813,8 +821,9 @@ class SampleTables: public CHeapObj { SampleTables() : _short_term_table(short_term_tablesize()), - _long_term_table(long_term_tablesize), - _count(0) + _long_term_table(long_term_tablesize), + _extremum_samples(NULL), + _count(0) {} void add_sample(const Sample* sample) { @@ -829,6 +838,34 @@ class SampleTables: public CHeapObj { if ((_count % (long_term_sample_interval / VitalsSampleInterval)) == 0) { _long_term_table.add_sample(sample); } + + // Update peak samples if needed. + if (StorePeakSamples) { + if (_extremum_samples == NULL) { + // For the first sample just initialize with the sample for all values we store peaks for. + _extremum_samples = (Sample*)NEW_C_HEAP_ARRAY(char, Sample::size_in_bytes() * Sample::num_values(), mtInternal); + + for (int i = 0; i < Sample::num_values(); ++i) { + ::memcpy(i * Sample::size_in_bytes() + (char*) _extremum_samples, sample, Sample::size_in_bytes()); + } + } + else { + // Otherwise iterate columns and update if needed. + for (Column const* column = ColumnList::the_list()->first(); column != NULL; column = column->next()) { + if (column->extremum() != NONE) { + int idx = column->index(); + Sample* extremum_sample = (Sample*)(idx * Sample::size_in_bytes() + (char*) _extremum_samples); + + bool should_log = (column->extremum() == MAX) && (sample->value(idx) > extremum_sample->value(idx)); + should_log |= (column->extremum() == MIN) && (sample->value(idx) < extremum_sample->value(idx)); + + if (should_log) { + ::memcpy(extremum_sample, sample, Sample::size_in_bytes()); + } + } + } + } + } } void print_all(outputStream* external_stream, const print_info_t* pi, const Sample* sample_now) { @@ -874,6 +911,18 @@ class SampleTables: public CHeapObj { st->cr(); } + if (StorePeakSamples) { + st->print_cr("Samples at extremes (+ marks a maximum, - marks a minimum)"); + print_headers(st, &widths, pi, 1); // Need more space for the mark to display. + + for (Column const* column = ColumnList::the_list()->first(); column != NULL; column = column->next()) { + if (column->extremum() != NONE) { + Sample* peak_sample = (Sample*) (column->index() * Sample::size_in_bytes() + (char*) _extremum_samples); + print_one_sample(st, peak_sample, NULL, &widths, pi, column->index(), column->extremum() == MIN ? '-' : '+'); + } + } + } + st->cr(); } // lock end diff --git a/src/hotspot/share/vitals/vitals_internals.hpp b/src/hotspot/share/vitals/vitals_internals.hpp index a43711118ed..e69f49e7006 100644 --- a/src/hotspot/share/vitals/vitals_internals.hpp +++ b/src/hotspot/share/vitals/vitals_internals.hpp @@ -61,6 +61,12 @@ namespace sapmachine_vitals { class ColumnList; + enum Extremum { + NONE, + MAX, + MIN + }; + class Column: public CHeapObj { friend class ColumnList; @@ -68,6 +74,7 @@ namespace sapmachine_vitals { const char* const _header; // optional. May be NULL. const char* const _name; const char* const _description; + const Extremum _extremum; // The following members are fixed by ColumnList when the Column is added to it. Column* _next; // next column in table @@ -80,7 +87,7 @@ namespace sapmachine_vitals { protected: - Column(const char* category, const char* header, const char* name, const char* description); + Column(const char* category, const char* header, const char* name, const char* description, Extremum extremum); // Child classes implement this. // output stream can be NULL; in that case, method shall return number of characters it would have printed. @@ -93,6 +100,7 @@ namespace sapmachine_vitals { const char* header() const { return _header; } const char* name() const { return _name; } const char* description() const { return _description; } + Extremum extremum() const { return _extremum; } void print_value(outputStream* os, value_t value, value_t last_value, int last_value_age, int min_width, const print_info_t* pi) const; @@ -110,6 +118,7 @@ namespace sapmachine_vitals { virtual bool is_memory_size() const { return false; } + static Extremum extremum_default() { return NONE; } }; // Some standard column types @@ -118,8 +127,8 @@ namespace sapmachine_vitals { int do_print0(outputStream* os, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const; public: - PlainValueColumn(const char* category, const char* header, const char* name, const char* description) - : Column(category, header, name, description) + PlainValueColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) {} }; @@ -128,8 +137,8 @@ namespace sapmachine_vitals { int last_value_age, const print_info_t* pi) const; public: // only_positive: only positive deltas are shown, negative deltas are supressed - DeltaValueColumn(const char* category, const char* header, const char* name, const char* description) - : Column(category, header, name, description) + DeltaValueColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) {} }; @@ -137,18 +146,19 @@ namespace sapmachine_vitals { int do_print0(outputStream* os, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const; public: - MemorySizeColumn(const char* category, const char* header, const char* name, const char* description) - : Column(category, header, name, description) + MemorySizeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) {} bool is_memory_size() const { return true; } + static Extremum extremum_default() { return MAX; } }; class DeltaMemorySizeColumn: public Column { int do_print0(outputStream* os, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const; public: - DeltaMemorySizeColumn(const char* category, const char* header, const char* name, const char* description) - : Column(category, header, name, description) + DeltaMemorySizeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) {} }; @@ -156,8 +166,8 @@ namespace sapmachine_vitals { int do_print0(outputStream* os, value_t value, value_t last_value, int last_value_age, const print_info_t* pi) const; public: - TimeStampColumn(const char* category, const char* header, const char* name, const char* description) - : Column(category, header, name, description) + TimeStampColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum) + : Column(category, header, name, description, extremum) {} }; @@ -218,11 +228,11 @@ namespace sapmachine_vitals { Column* define_column ( const char* const category, const char* const header, const char* const name, const char* const description, - bool is_active) + bool is_active, Extremum extremum = ColumnType::extremum_default()) { Column* c = NULL; if (is_active) { - c = new ColumnType(category, header, name, description); + c = new ColumnType(category, header, name, description, extremum); ColumnList::the_list()->add_column(c); } Legend::the_legend()->add_column_info(category, header, name, description);