diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index cb73f17c0c..cd17c7e963 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -55,7 +55,7 @@ _systemd_analyze() { ) local -A VERBS=( - [STANDALONE]='time blame plot dump unit-paths calendar timespan condition' + [STANDALONE]='time blame plot dump unit-paths calendar timespan timestamp condition' [CRITICAL_CHAIN]='critical-chain' [DOT]='dot' [LOG_LEVEL]='log-level' diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 97642660e4..b7545c08e2 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -26,6 +26,7 @@ #include "def.h" #include "fd-util.h" #include "fileio.h" +#include "format-table.h" #include "glob-util.h" #include "hashmap.h" #include "locale-util.h" @@ -204,10 +205,6 @@ static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char return 0; } -static int compare_unit_time(const struct unit_times *a, const struct unit_times *b) { - return CMP(b->time, a->time); -} - static int compare_unit_start(const struct unit_times *a, const struct unit_times *b) { return CMP(a->activating, b->activating); } @@ -1070,7 +1067,9 @@ static int analyze_critical_chain(int argc, char *argv[], void *userdata) { static int analyze_blame(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(unit_times_freep) struct unit_times *times = NULL; + _cleanup_(table_unrefp) Table *table = NULL; struct unit_times *u; + TableCell *cell; int n, r; r = acquire_bus(&bus, NULL); @@ -1081,18 +1080,50 @@ static int analyze_blame(int argc, char *argv[], void *userdata) { if (n <= 0) return n; - typesafe_qsort(times, n, compare_unit_time); + table = table_new("TIME", "UNIT"); + if (!table) + return log_oom(); + + table_set_header(table, false); + + assert_se(cell = table_get_cell(table, 0, 0)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_set_align_percent(table, cell, 100); + if (r < 0) + return r; + + assert_se(cell = table_get_cell(table, 0, 1)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_set_sort(table, 0, SIZE_MAX); + if (r < 0) + return r; + + r = table_set_reverse(table, 0, true); + if (r < 0) + return r; + + for (u = times; u->has_data; u++) { + if (u->time <= 0) + continue; + + r = table_add_cell(table, NULL, TABLE_TIMESPAN_MSEC, &u->time); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, u->name); + if (r < 0) + return r; + } (void) pager_open(arg_pager_flags); - for (u = times; u->has_data; u++) { - char ts[FORMAT_TIMESPAN_MAX]; - - if (u->time > 0) - printf("%16s %s\n", format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC), u->name); - } - - return 0; + return table_print(table, NULL); } static int analyze_time(int argc, char *argv[], void *userdata) { @@ -1690,9 +1721,10 @@ static int dump_timespan(int argc, char *argv[], void *userdata) { char **input_timespan; STRV_FOREACH(input_timespan, strv_skip(argv, 1)) { + _cleanup_(table_unrefp) Table *table = NULL; + usec_t output_usecs; + TableCell *cell; int r; - usec_t usec_magnitude = 1, output_usecs; - char ft_buf[FORMAT_TIMESPAN_MAX]; r = parse_time(*input_timespan, &output_usecs, USEC_PER_SEC); if (r < 0) { @@ -1701,12 +1733,57 @@ static int dump_timespan(int argc, char *argv[], void *userdata) { return r; } - printf("Original: %s\n", *input_timespan); - printf(" %ss: %" PRIu64 "\n", special_glyph(SPECIAL_GLYPH_MU), output_usecs); - printf(" Human: %s%s%s\n", - ansi_highlight(), - format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude), - ansi_normal()); + table = table_new("NAME", "VALUE"); + if (!table) + return log_oom(); + + table_set_header(table, false); + + assert_se(cell = table_get_cell(table, 0, 0)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_set_align_percent(table, cell, 100); + if (r < 0) + return r; + + assert_se(cell = table_get_cell(table, 0, 1)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, "Original:"); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, *input_timespan); + if (r < 0) + return r; + + r = table_add_cell_stringf(table, NULL, "%ss:", special_glyph(SPECIAL_GLYPH_MU)); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_UINT64, &output_usecs); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, "Human:"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESPAN, &output_usecs); + if (r < 0) + return r; + + r = table_set_color(table, cell, ansi_highlight()); + if (r < 0) + return r; + + r = table_print(table, NULL); + if (r < 0) + return r; if (input_timespan[1]) putchar('\n'); @@ -1716,8 +1793,9 @@ static int dump_timespan(int argc, char *argv[], void *userdata) { } static int test_timestamp_one(const char *p) { + _cleanup_(table_unrefp) Table *table = NULL; + TableCell *cell; usec_t usec; - char buf[FORMAT_TIMESTAMP_MAX]; int r; r = parse_timestamp(p, &usec); @@ -1727,24 +1805,77 @@ static int test_timestamp_one(const char *p) { return r; } - printf(" Original form: %s\n", p); - printf("Normalized form: %s%s%s\n", - ansi_highlight_blue(), - format_timestamp(buf, sizeof buf, usec), - ansi_normal()); + table = table_new("NAME", "VALUE"); + if (!table) + return log_oom(); - if (!in_utc_timezone()) - printf(" (in UTC): %s\n", format_timestamp_utc(buf, sizeof buf, usec)); + table_set_header(table, false); - printf(" UNIX seconds: @%"PRI_USEC"%s%0*"PRI_USEC"\n", - usec / USEC_PER_SEC, - usec % USEC_PER_SEC ? "." : "", - usec % USEC_PER_SEC ? 6 : 0, - usec % USEC_PER_SEC); + assert_se(cell = table_get_cell(table, 0, 0)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; - printf(" From now: %s\n", format_timestamp_relative(buf, sizeof buf, usec)); + r = table_set_align_percent(table, cell, 100); + if (r < 0) + return r; - return 0; + assert_se(cell = table_get_cell(table, 0, 1)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, "Original form:"); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, p); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, "Normalized form:"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &usec); + if (r < 0) + return r; + + r = table_set_color(table, cell, ansi_highlight_blue()); + if (r < 0) + return r; + + if (!in_utc_timezone()) { + r = table_add_cell(table, NULL, TABLE_STRING, "(in UTC):"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESTAMP_UTC, &usec); + if (r < 0) + return r; + } + + r = table_add_cell(table, NULL, TABLE_STRING, "UNIX seconds:"); + if (r < 0) + return r; + + r = table_add_cell_stringf(table, &cell, "@%"PRI_USEC"%s%0*"PRI_USEC"", + usec / USEC_PER_SEC, + usec % USEC_PER_SEC ? "." : "", + usec % USEC_PER_SEC ? 6 : 0, + usec % USEC_PER_SEC); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, "From now:"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESTAMP_RELATIVE, &usec); + if (r < 0) + return r; + + return table_print(table, NULL); } static int test_timestamp(int argc, char *argv[], void *userdata) { @@ -1765,7 +1896,9 @@ static int test_timestamp(int argc, char *argv[], void *userdata) { static int test_calendar_one(usec_t n, const char *p) { _cleanup_(calendar_spec_freep) CalendarSpec *spec = NULL; + _cleanup_(table_unrefp) Table *table = NULL; _cleanup_free_ char *t = NULL; + TableCell *cell; int r; r = calendar_spec_from_string(p, &spec); @@ -1779,30 +1912,80 @@ static int test_calendar_one(usec_t n, const char *p) { if (r < 0) return log_error_errno(r, "Failed to format calendar specification '%s': %m", p); - if (!streq(t, p)) - printf(" Original form: %s\n", p); + table = table_new("NAME", "VALUE"); + if (!table) + return log_oom(); - printf("Normalized form: %s\n", t); + table_set_header(table, false); + + assert_se(cell = table_get_cell(table, 0, 0)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + r = table_set_align_percent(table, cell, 100); + if (r < 0) + return r; + + assert_se(cell = table_get_cell(table, 0, 1)); + r = table_set_ellipsize_percent(table, cell, 100); + if (r < 0) + return r; + + if (!streq(t, p)) { + r = table_add_cell(table, NULL, TABLE_STRING, "Original form:"); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, p); + if (r < 0) + return r; + } + + r = table_add_cell(table, NULL, TABLE_STRING, "Normalized form:"); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_STRING, t); + if (r < 0) + return r; for (unsigned i = 0; i < arg_iterations; i++) { - char buffer[CONST_MAX(FORMAT_TIMESTAMP_MAX, FORMAT_TIMESTAMP_RELATIVE_MAX)]; usec_t next; r = calendar_spec_next_usec(spec, n, &next); if (r == -ENOENT) { - if (i == 0) - printf(" Next elapse: %snever%s\n", ansi_highlight_yellow(), ansi_normal()); - return 0; + if (i == 0) { + r = table_add_cell(table, NULL, TABLE_STRING, "Next elapse:"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_STRING, "never"); + if (r < 0) + return r; + + r = table_set_color(table, cell, ansi_highlight_yellow()); + if (r < 0) + return r; + } + break; } if (r < 0) return log_error_errno(r, "Failed to determine next elapse for '%s': %m", p); - if (i == 0) - printf(" Next elapse: %s%s%s\n", - ansi_highlight_blue(), - format_timestamp(buffer, sizeof(buffer), next), - ansi_normal()); - else { + if (i == 0) { + r = table_add_cell(table, NULL, TABLE_STRING, "Next elapse:"); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &next); + if (r < 0) + return r; + + r = table_set_color(table, cell, ansi_highlight_blue()); + if (r < 0) + return r; + } else { int k = DECIMAL_STR_WIDTH(i + 1); if (k < 8) @@ -1810,20 +1993,41 @@ static int test_calendar_one(usec_t n, const char *p) { else k = 0; - printf("%*sIter. #%u: %s%s%s\n", - k, "", i+1, - ansi_highlight_blue(), format_timestamp(buffer, sizeof(buffer), next), ansi_normal()); + r = table_add_cell_stringf(table, NULL, "Iter. #%u:", i+1); + if (r < 0) + return r; + + r = table_add_cell(table, &cell, TABLE_TIMESTAMP, &next); + if (r < 0) + return r; + + r = table_set_color(table, cell, ansi_highlight_blue()); + if (r < 0) + return r; } - if (!in_utc_timezone()) - printf(" (in UTC): %s\n", format_timestamp_utc(buffer, sizeof(buffer), next)); + if (!in_utc_timezone()) { + r = table_add_cell(table, NULL, TABLE_STRING, "(in UTC):"); + if (r < 0) + return r; - printf(" From now: %s\n", format_timestamp_relative(buffer, sizeof(buffer), next)); + r = table_add_cell(table, NULL, TABLE_TIMESTAMP_UTC, &next); + if (r < 0) + return r; + } + + r = table_add_cell(table, NULL, TABLE_STRING, "From now:"); + if (r < 0) + return r; + + r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE, &next); + if (r < 0) + return r; n = next; } - return 0; + return table_print(table, NULL); } static int test_calendar(int argc, char *argv[], void *userdata) { diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 54ca1972bd..11e4d56729 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -230,7 +230,10 @@ static size_t table_data_size(TableDataType type, const void *data) { return sizeof(bool); case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: return sizeof(usec_t); case TABLE_SIZE: @@ -719,7 +722,10 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { break; case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: buffer.usec = va_arg(ap, usec_t); data = &buffer.usec; break; @@ -882,9 +888,12 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t return 0; case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: return CMP(a->timestamp, b->timestamp); case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: return CMP(a->timespan, b->timespan); case TABLE_SIZE: @@ -985,28 +994,39 @@ static const char *table_data_format(TableData *d) { case TABLE_BOOLEAN: return yes_no(d->boolean); - case TABLE_TIMESTAMP: { + case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: { _cleanup_free_ char *p; + char *ret; p = new(char, FORMAT_TIMESTAMP_MAX); if (!p) return NULL; - if (!format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp)) + if (d->type == TABLE_TIMESTAMP) + ret = format_timestamp(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + else if (d->type == TABLE_TIMESTAMP_UTC) + ret = format_timestamp_utc(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + else + ret = format_timestamp_relative(p, FORMAT_TIMESTAMP_MAX, d->timestamp); + if (!ret) return "n/a"; d->formatted = TAKE_PTR(p); break; } - case TABLE_TIMESPAN: { + case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: { _cleanup_free_ char *p; p = new(char, FORMAT_TIMESPAN_MAX); if (!p) return NULL; - if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, 0)) + if (!format_timespan(p, FORMAT_TIMESPAN_MAX, d->timespan, + d->type == TABLE_TIMESPAN ? 0 : USEC_PER_MSEC)) return "n/a"; d->formatted = TAKE_PTR(p); @@ -1632,12 +1652,15 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) { return json_variant_new_boolean(ret, d->boolean); case TABLE_TIMESTAMP: + case TABLE_TIMESTAMP_UTC: + case TABLE_TIMESTAMP_RELATIVE: if (d->timestamp == USEC_INFINITY) return json_variant_new_null(ret); return json_variant_new_unsigned(ret, d->timestamp); case TABLE_TIMESPAN: + case TABLE_TIMESPAN_MSEC: if (d->timespan == USEC_INFINITY) return json_variant_new_null(ret); diff --git a/src/shared/format-table.h b/src/shared/format-table.h index ada59f3423..bbfeb10c11 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -13,7 +13,10 @@ typedef enum TableDataType { TABLE_STRING, TABLE_BOOLEAN, TABLE_TIMESTAMP, + TABLE_TIMESTAMP_UTC, + TABLE_TIMESTAMP_RELATIVE, TABLE_TIMESPAN, + TABLE_TIMESPAN_MSEC, TABLE_SIZE, TABLE_BPS, TABLE_INT,