Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SapMachine (21) #1449: Store vitals samples for max/min values #1457

Merged
merged 1 commit into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading