Skip to content

Commit

Permalink
Make printing the vitals more robust
Browse files Browse the repository at this point in the history
  • Loading branch information
schmelter-sap committed Jul 7, 2023
1 parent 86ff458 commit 100a437
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 37 deletions.
110 changes: 73 additions & 37 deletions src/hotspot/share/vitals/vitals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,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[128]; // 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]) >= Legend::the_legend()->nr_of_columns(), "array to 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 @@ -212,7 +215,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 @@ -223,7 +226,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 @@ -250,7 +253,7 @@ 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) {
Expand All @@ -260,6 +263,7 @@ void Legend::add_column_info(const char* const category, const char* const heade
_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 @@ -317,7 +321,7 @@ void ColumnList::add_column(Column* c) {

////////////////////

static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width) {
static void print_category_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {

assert(pi->csv == false, "Not in csv mode");
ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
Expand All @@ -336,7 +340,7 @@ static void print_category_line(outputStream* st, const ColumnWidths* widths, co
}
width = 0;
}
width += widths->at(c->index()) + add_width;
width += widths->at(c->index());
width += 1; // divider between columns
last_category_text = c->category();
c = c->next();
Expand All @@ -345,7 +349,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, int add_width) {
static void print_header_line(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {

assert(pi->csv == false, "Not in csv mode");
ostream_put_n(st, ' ', TIMESTAMP_LEN + TIMESTAMP_DIVIDER_LEN);
Expand All @@ -369,7 +373,7 @@ static void print_header_line(outputStream* st, const ColumnWidths* widths, cons
}
width = 0;
}
width += widths->at(c->index()) + add_width;
width += widths->at(c->index());
width += 1; // divider between columns
last_header_text = c->header();
c = c->next();
Expand All @@ -380,7 +384,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, int add_width) {
static void print_column_names(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {

// Leave space for timestamp column
if (pi->csv == false) {
Expand All @@ -393,7 +397,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()) + add_width, c->name());
st->print("%-*s ", widths->at(c->index()), c->name());
} else { // csv mode
// csv: use comma as delimiter, don't pad, and precede name with category/header
// (limited to 4 chars).
Expand Down Expand Up @@ -606,7 +610,7 @@ 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);
const int min_width = widths->at(idx) - (marked_index >= 0 ? 1 : 0);
c->print_value(st, v, v2, age, min_width, pi);
if (marked_index >= 0) {
st->put(marked_index == idx ? mark : ' ');
Expand Down Expand Up @@ -756,6 +760,19 @@ class PrintSamplesClosure : public SampleTable::Closure {
}
};

static void dump_buffer(stringStream* in, outputStream* out, int max_size) {
g_vitals_lock.unlock();
out->print_raw(in->base());
in->reset();

if (in->size() >= max_size - 1) {
out->cr();
out->print_cr("-- Buffer overflow, truncated (total: " SIZE_FORMAT ").", (size_t) in->count());
}

g_vitals_lock.lock();
}

// 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<mtInternal> {
Expand All @@ -779,7 +796,7 @@ class SampleTables : public CHeapObj<mtInternal> {

// 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[512 * K];
char _temp_buffer[32 * K];

static void print_table(const SampleTable* table, outputStream* st,
const ColumnWidths* widths, const print_info_t* pi) {
Expand All @@ -791,12 +808,12 @@ class SampleTables : public CHeapObj<mtInternal> {
table->walk_table_locked(&prclos, !pi->reverse_ordering);
}

static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi, int add_width = 0) {
static void print_headers(outputStream* st, const ColumnWidths* widths, const print_info_t* pi) {
if (pi->csv == false) {
print_category_line(st, widths, pi, add_width);
print_header_line(st, widths, pi, add_width);
print_category_line(st, widths, pi);
print_header_line(st, widths, pi);
}
print_column_names(st, widths, pi, add_width);
print_column_names(st, widths, pi);
}

// Helper, print a time span given in seconds-
Expand Down Expand Up @@ -888,55 +905,74 @@ class SampleTables : public CHeapObj<mtInternal> {
// 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));
stringStream* st = &sstrm;

{ // 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);
// We dump each table seperately to save memory in our preallocated buffer. Since we have
// to give up the lock for a short time, we don't get a consistent snapshot, but it should
// not be a big deal.
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();
dump_buffer(st, external_stream, sizeof(_temp_buffer));
}

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();
dump_buffer(st, external_stream, sizeof(_temp_buffer));
}
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();
dump_buffer(st, external_stream, sizeof(_temp_buffer));
}

if (StoreVitalsExtremas && (_extremum_samples != NULL) && (_last_extremum_samples != NULL)) {
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.
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 = (Sample*)(column->index() * Sample::size_in_bytes() + (char*)_extremum_samples);
Sample* last_extremum_sample = (Sample*)(column->index() * Sample::size_in_bytes() + (char*)_last_extremum_samples);
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 = (Sample*) (column->index() * Sample::size_in_bytes() + (char*) _extremum_samples);
Sample* last_extremum_sample = (Sample*) (column->index() * Sample::size_in_bytes() + (char*) _last_extremum_samples);
Sample* extremum_sample = (Sample*)(column->index() * Sample::size_in_bytes() + (char*)_extremum_samples);
Sample* last_extremum_sample = (Sample*)(column->index() * Sample::size_in_bytes() + (char*)_last_extremum_samples);
print_one_sample(st, extremum_sample, last_extremum_sample, &widths, pi, column->index(), column->extremum() == MIN ? '-' : '+');
}
}

dump_buffer(st, external_stream, sizeof(_temp_buffer));
}

st->cr();
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/vitals/vitals_internals.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,12 +179,14 @@ namespace sapmachine_vitals {
static Legend* _the_legend;
// needed during building the legend
const char* _last_added_cat;
int _nr_of_columns;
public:
Legend();
void add_column_info(const char* const category, const char* const header,
const char* const name, const char* const description);
void add_footnote(const char* text);
void print_on(outputStream* st) const;
int nr_of_columns() { return _nr_of_columns; }
static Legend* the_legend () { return _the_legend; }
static bool initialize();
};
Expand Down

0 comments on commit 100a437

Please sign in to comment.