Skip to content

Commit

Permalink
SapMachine #1449: Store vitals samples for max/min values
Browse files Browse the repository at this point in the history
(cherry picked from commit ee6966d)
  • Loading branch information
schmelter-sap authored and RealCLanger committed Jul 27, 2023
1 parent 480f33c commit c583776
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 72 deletions.
10 changes: 5 additions & 5 deletions src/hotspot/os/linux/vitals_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ class CPUTimeColumn: public Column {
}

public:
CPUTimeColumn(const char* category, const char* header, const char* name, const char* description)
: Column(category, header, name, description)
CPUTimeColumn(const char* category, const char* header, const char* name, const char* description, Extremum extremum)
: Column(category, header, name, description, extremum)
{
_clk_tck = ::sysconf(_SC_CLK_TCK);
_num_cores = os::active_processor_count();
Expand Down Expand Up @@ -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<MemorySizeColumn>(system_cat, NULL, "avail", "Memory available without swapping [host] [krn]", g_show_system_memavail);
define_column<MemorySizeColumn>(system_cat, NULL, "avail", "Memory available without swapping [host] [krn]", g_show_system_memavail, MIN);
g_col_system_memcommitted =
define_column<MemorySizeColumn>(system_cat, NULL, "comm", "Committed memory [host]", true);
g_col_system_memcommitted_ratio =
Expand Down Expand Up @@ -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<MemorySizeColumn>(system_cat, "cgroup", "lim", "cgroup memory limit [cgrp]", g_show_cgroup_info);
define_column<MemorySizeColumn>(system_cat, "cgroup", "lim", "cgroup memory limit [cgrp]", g_show_cgroup_info, MIN);
g_col_system_cgrp_soft_limit_in_bytes =
define_column<MemorySizeColumn>(system_cat, "cgroup", "slim", "cgroup memory soft limit [cgrp]", g_show_cgroup_info);
define_column<MemorySizeColumn>(system_cat, "cgroup", "slim", "cgroup memory soft limit [cgrp]", g_show_cgroup_info, MIN);
g_col_system_cgrp_usage_in_bytes =
define_column<MemorySizeColumn>(system_cat, "cgroup", "usg", "cgroup memory usage [cgrp]", g_show_cgroup_info);
g_col_system_cgrp_kmem_usage_in_bytes =
Expand Down
4 changes: 2 additions & 2 deletions src/hotspot/os/windows/vitals_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ static Column* g_col_process_commit_charge = NULL;

bool platform_columns_initialize() {
g_col_system_memoryload =
define_column<PlainValueColumn>("system", NULL, "mload", "Approximate percentage of physical memory that is in use.", true);
define_column<PlainValueColumn>("system", NULL, "mload", "Approximate percentage of physical memory that is in use.", true, MAX);

// MEMORYSTATUSEX ullAvailPhys
g_col_system_avail_phys =
define_column<MemorySizeColumn>("system", NULL, "avail-phys", "Amount of physical memory currently available.", true);
define_column<MemorySizeColumn>("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<MemorySizeColumn>("system", NULL, "wset", "Working set size", true);
Expand Down
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/globals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ const int ObjectAlignmentInBytes = 8;
product(bool, PrintVitalsAtExit, false, \
"Prints vitals at VM exit to tty.") \
\
product(bool, StoreVitalsExtremas, true, \
"If enabled we store the samples for extremum values of " \
"selected types.") \
\
product(ccstr, VitalsFile, NULL, \
"When DumpVitalsAtExit is set, the file name prefix for the " \
"output files (default is sapmachine_vitals_<pid>).") \
Expand Down
158 changes: 108 additions & 50 deletions src/hotspot/share/vitals/vitals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,16 @@ static void print_timestamp(outputStream* st, time_t t) {
// Keeps an array of ints, dynamically sized (since each platform has a different number of columns),
// and offers methods of auto-sizeing them to fit given samples (via dry-printing).
class ColumnWidths {
int* _widths;
int _widths[64]; // Don't allocate dynamically, since we might not have enough memory when we use it.

public:

ColumnWidths() {
// Assert including the non-active columns, so we spot possible problems earlier.
assert(sizeof(_widths) / sizeof(_widths[0]) >= (size_t) Legend::the_legend()->nr_of_columns(), "array too small");

// Allocate array; initialize with the minimum required column widths (which is the
// size required to print the column header fully)
_widths = NEW_C_HEAP_ARRAY(int, ColumnList::the_list()->num_columns(), mtInternal);
const Column* c = ColumnList::the_list()->first();
while (c != NULL) {
_widths[c->index()] = (int)::strlen(c->name());
Expand All @@ -228,7 +231,7 @@ class ColumnWidths {

// given a sample (and an optional preceding sample for delta values),
// update widths to accommodate sample values (uses dry-printing)
void update_from_sample(const Sample* sample, const Sample* last_sample, const print_info_t* pi) {
void update_from_sample(const Sample* sample, const Sample* last_sample, const print_info_t* pi, int add_width = 0) {
const Column* c = ColumnList::the_list()->first();
while (c != NULL) {
const int idx = c->index();
Expand All @@ -239,7 +242,7 @@ class ColumnWidths {
v2 = last_sample->value(idx);
age = sample->timestamp() - last_sample->timestamp();
}
int needed = c->calc_print_size(v, v2, age, pi);
int needed = c->calc_print_size(v, v2, age, pi) + add_width;
if (_widths[idx] < needed) {
_widths[idx] = needed;
}
Expand All @@ -266,16 +269,17 @@ stringStream _legend;
stringStream _footnote;
static Legend* _the_legend;

Legend::Legend() : _last_added_cat(NULL) {}
Legend::Legend() : _last_added_cat(NULL), _nr_of_columns(0) {}

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();
}
_last_added_cat = category;
_nr_of_columns++;
// print column name and description
const int min_width_column_label = 16;
char buf[32];
Expand Down Expand Up @@ -503,17 +507,18 @@ 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)
{}

void Column::print_value(outputStream* st, value_t value, value_t last_value,
int last_value_age, int min_width, const print_info_t* pi) const {
int last_value_age, int min_width, const print_info_t* pi, char const* marker) const {

// We print all values right aligned.
int needed = calc_print_size(value, last_value, last_value_age, pi);
Expand All @@ -526,6 +531,7 @@ void Column::print_value(outputStream* st, value_t value, value_t last_value,
st->put('"');
}
do_print(st, value, last_value, last_value_age, pi);
st->print_raw(marker);
if (pi->csv) {
st->put('"');
}
Expand Down Expand Up @@ -594,7 +600,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 const* mark = NULL) {

// Print timestamp and divider
if (pi->csv) {
Expand All @@ -621,8 +627,9 @@ static void print_one_sample(outputStream* st, const Sample* sample,
v2 = last_sample->value(idx);
age = sample->timestamp() - last_sample->timestamp();
}
const int min_width = widths->at(idx);
c->print_value(st, v, v2, age, min_width, pi);
const int min_width = widths->at(idx) - (marked_index >= 0 ? 1 : 0);
c->print_value(st, v, v2, age, min_width, pi,
marked_index == idx ? mark : (marked_index >= 0 && !pi->csv ? " " : ""));
st->put(pi->csv ? ',' : ' ');
c = c->next();
}
Expand Down Expand Up @@ -650,8 +657,6 @@ class SampleTable : public CHeapObj<mtInternal> {
assert(idx >= 0 && idx <= _num_entries, "invalid index: %d", idx);
return Sample::size_in_bytes() * idx;
}
const Sample* sample_at(int index) const { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }
Sample* sample_at(int index) { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }

public:

Expand All @@ -671,6 +676,9 @@ class SampleTable : public CHeapObj<mtInternal> {

bool is_empty() const { return _head == -1; }

const Sample* sample_at(int index) const { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }
Sample* sample_at(int index) { return (Sample*)((uint8_t*)_samples + sample_offset_in_bytes(index)); }

void add_sample(const Sample* sample) {
// Advance head
_head ++;
Expand Down Expand Up @@ -784,13 +792,11 @@ class SampleTables: public CHeapObj<mtInternal> {

SampleTable _short_term_table;
SampleTable _long_term_table;
SampleTable _extremum_samples;
SampleTable _last_extremum_samples;

int _count;

// A pre-allocated buffer for printing reports. We preallocate this since
// when we want to print the report we may be in no condition to allocate memory.
char _temp_buffer[196 * K];

static void print_table(const SampleTable* table, outputStream* st,
const ColumnWidths* widths, const print_info_t* pi) {
if (table->is_empty()) {
Expand Down Expand Up @@ -830,6 +836,8 @@ class SampleTables: public CHeapObj<mtInternal> {
SampleTables()
: _short_term_table(short_term_tablesize()),
_long_term_table(long_term_tablesize),
_extremum_samples(Sample::num_values()),
_last_extremum_samples(Sample::num_values()),
_count(0)
{}

Expand All @@ -845,64 +853,116 @@ class SampleTables: public CHeapObj<mtInternal> {
if ((_count % (long_term_sample_interval / VitalsSampleInterval)) == 0) {
_long_term_table.add_sample(sample);
}
}

void print_all(outputStream* external_stream, const print_info_t* pi, const Sample* sample_now) {
// Update exetremum samples if needed.
if (StoreVitalsExtremas) {
static Sample* last_sample = NULL;

if (last_sample == NULL) {
// Nothing to do yet. We need at least two samples, since some types need the
// previous sample to print. Just allocate the space for the last sample as a marker
// for seeing the first sample.
last_sample = (Sample*) NEW_C_HEAP_ARRAY(char, Sample::size_in_bytes(), mtInternal);
} else if (_extremum_samples.is_empty()) {
// We already have a last sample and this is the second sample we see.
// We can initialize the two tables now to store the last sample and this sample
// for all extremas.
for (int i = 0; i < Sample::num_values(); ++i) {
_last_extremum_samples.add_sample(last_sample);
_extremum_samples.add_sample(sample);
}
} else {
// 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 = _extremum_samples.sample_at(idx);

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) {
Sample* last_extremum_sample = _last_extremum_samples.sample_at(idx);
::memcpy(last_extremum_sample, last_sample, Sample::size_in_bytes());
::memcpy(extremum_sample, sample, Sample::size_in_bytes());
}
}
}
}

// Remember the last sample.
::memcpy(last_sample, sample, Sample::size_in_bytes());
}
}

// We are paranoid about blocking inside a lock. So we print to a preallocated buffer under
// lock protection, and copy ot the outside stream when out of the lock.
stringStream sstrm(_temp_buffer, sizeof(_temp_buffer));
void print_all(outputStream* st, const print_info_t* pi, const Sample* sample_now) {

{ // lock start
AutoLock autolock(&g_vitals_lock);

outputStream* st = &sstrm;

// Pre-calc column widths needed to display all tables and values nicely aligned
ColumnWidths widths;

MeasureColumnWidthsClosure mcwclos(pi, &widths);
_short_term_table.walk_table_locked(&mcwclos);
_long_term_table.walk_table_locked(&mcwclos);
if (sample_now != NULL) {
ColumnWidths widths;
MeasureColumnWidthsClosure mcwclos(pi, &widths);
widths.update_from_sample(sample_now, NULL, pi);
}

// Now print
if (sample_now != NULL) {
st->print_cr("Now:");
print_headers(st, &widths, pi);
print_one_sample(st, sample_now, NULL, &widths, pi);
st->cr();
}

if (pi->csv == false) {
print_time_span(st, short_term_span_seconds);
if (!_short_term_table.is_empty()) {
ColumnWidths widths;
MeasureColumnWidthsClosure mcwclos(pi, &widths);
_short_term_table.walk_table_locked(&mcwclos);

if (pi->csv == false) {
print_time_span(st, short_term_span_seconds);
}
print_headers(st, &widths, pi);
print_table(&_short_term_table, st, &widths, pi);
st->cr();
}
print_headers(st, &widths, pi);
print_table(&_short_term_table, st, &widths, pi);
st->cr();

if (!_long_term_table.is_empty()) {
ColumnWidths widths;
MeasureColumnWidthsClosure mcwclos(pi, &widths);
_long_term_table.walk_table_locked(&mcwclos);
print_time_span(st, long_term_span_seconds);
print_headers(st, &widths, pi);
print_table(&_long_term_table, st, &widths, pi);
st->cr();
}

if (StoreVitalsExtremas && !_extremum_samples.is_empty() && !_last_extremum_samples.is_empty()) {
st->print_cr("Samples at extremes (+ marks a maximum, - marks a minimum)");

ColumnWidths widths;
MeasureColumnWidthsClosure mcwclos(pi, &widths);

for (Column const* column = ColumnList::the_list()->first(); column != NULL; column = column->next()) {
if (column->extremum() != NONE) {
Sample* extremum_sample = _extremum_samples.sample_at(column->index());
Sample* last_extremum_sample = _last_extremum_samples.sample_at(column->index());
widths.update_from_sample(extremum_sample, last_extremum_sample, pi, 1);
}
}

print_headers(st, &widths, pi); // 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* extremum_sample = _extremum_samples.sample_at(column->index());
Sample* last_extremum_sample = _last_extremum_samples.sample_at(column->index());
print_one_sample(st, extremum_sample, last_extremum_sample, &widths, pi, column->index(),
column->extremum() == MIN ? "-" : "+");
}
}
}

st->cr();

} // lock end

// If this fires, enlarge the buffer size. Since the table sizes are static, output here cannot be endless,
// so there must be a number large enough to fit every possible report.
external_stream->print_raw(_temp_buffer);
if (sstrm.size() >= sizeof(_temp_buffer) - 1) {
external_stream->cr();
external_stream->print_cr("-- Buffer overflow, truncated (total: " SIZE_FORMAT ").", (size_t)sstrm.count());
}
}

};

static SampleTables* g_all_tables = NULL;
Expand Down Expand Up @@ -1349,7 +1409,6 @@ void print_report(outputStream* st, const print_info_t* pinfo) {
g_all_tables->print_all(st, &info, sample_now);

os::free(sample_now);

}

// Dump both textual and csv style reports to two files, "sapmachine_vitals_<pid>.txt" and "sapmachine_vitals_<pid>.csv".
Expand Down Expand Up @@ -1399,7 +1458,6 @@ void dump_reports() {
};
print_report(&fs, &settings);
}

}

// For printing in thread lists only.
Expand Down
Loading

0 comments on commit c583776

Please sign in to comment.