mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 08:25:20 +09:00
Merge pull request #25790 from joshua-zivkovic/JZ/plotjson-main
systemd-analyze: Add JSON and table output to systemd-analyze's plot
This commit is contained in:
@@ -274,8 +274,8 @@ Timestamp units-load-finish: Thu 2019-03-14 23:28:07 CET
|
||||
<refsect2>
|
||||
<title><command>systemd-analyze plot</command></title>
|
||||
|
||||
<para>This command prints an SVG graphic detailing which system services have been started at what
|
||||
time, highlighting the time they spent on initialization.</para>
|
||||
<para>This command prints either an SVG graphic, detailing which system services have been started at what
|
||||
time, highlighting the time they spent on initialization, or the raw time data in JSON or table format.</para>
|
||||
|
||||
<example>
|
||||
<title><command>Plot a bootchart</command></title>
|
||||
@@ -1211,7 +1211,17 @@ $ systemd-analyze verify /tmp/source:alias.service
|
||||
corresponds to a higher security threat. The JSON version of the table is printed to standard
|
||||
output. The <replaceable>MODE</replaceable> passed to the option can be one of three:
|
||||
<option>off</option> which is the default, <option>pretty</option> and <option>short</option>
|
||||
which respectively output a prettified or shorted JSON version of the security table.</para></listitem>
|
||||
which respectively output a prettified or shorted JSON version of the security table.
|
||||
|
||||
With the <command>plot</command> command, genereate a JSON formatted output of the raw time data.
|
||||
The format is a JSON array with objects containing the following fields: <varname>name</varname>
|
||||
which is the unit name, <varname>activated</varname> which is the time after startup the
|
||||
service was activated, <varname>activating</varname> which is how long after startup the service
|
||||
was initially started, <varname>time</varname> which is how long the service took to activate
|
||||
from when it was initially started, <varname>deactivated</varname> which is the time after startup
|
||||
that the service was deactivated, <varname>deactivating</varname> whcih is the time after startup
|
||||
that the service was initially told to deactivate.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
@@ -1242,6 +1252,21 @@ $ systemd-analyze verify /tmp/source:alias.service
|
||||
other paths.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--table</option></term>
|
||||
|
||||
<listitem><para>When used with the <command>plot</command> command, the raw time data is output in a table.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--no-legend</option></term>
|
||||
|
||||
<listitem><para>When used with the <command>plot</command> command in combination with either
|
||||
<option>--table</option> or <option>--json=</option>, no legends or hints are included in the output.
|
||||
</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="user-system-options.xml" xpointer="host" />
|
||||
<xi:include href="user-system-options.xml" xpointer="machine" />
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ _systemd_analyze() {
|
||||
)
|
||||
|
||||
local -A VERBS=(
|
||||
[STANDALONE]='time blame plot unit-paths exit-status calendar timestamp timespan'
|
||||
[STANDALONE]='time blame unit-paths exit-status calendar timestamp timespan'
|
||||
[CRITICAL_CHAIN]='critical-chain'
|
||||
[DOT]='dot'
|
||||
[DUMP]='dump'
|
||||
@@ -72,6 +72,7 @@ _systemd_analyze() {
|
||||
[SECURITY]='security'
|
||||
[CONDITION]='condition'
|
||||
[INSPECT_ELF]='inspect-elf'
|
||||
[PLOT]='plot'
|
||||
)
|
||||
|
||||
local CONFIGS='systemd/bootchart.conf systemd/coredump.conf systemd/journald.conf
|
||||
@@ -195,6 +196,11 @@ _systemd_analyze() {
|
||||
comps=$( compgen -A file -- "$cur" )
|
||||
compopt -o filenames
|
||||
fi
|
||||
|
||||
elif __contains_word "$verb" ${VERBS[PLOT]}; then
|
||||
if [[ $cur = -* ]]; then
|
||||
comps='--help --version --system --user --global --no-pager --json=off --json=pretty --json=short --table --no-legend'
|
||||
fi
|
||||
fi
|
||||
|
||||
COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
|
||||
|
||||
@@ -40,6 +40,13 @@
|
||||
_describe -t groups 'file system groups' _groups || compadd "$@"
|
||||
}
|
||||
|
||||
(( $+functions[_systemd-analyze_plot] )) ||
|
||||
_systemd-analyze_plot() {
|
||||
local -a _options
|
||||
_options=( '--json=off' '--json=pretty' '--json=short' '--table' '--no-legend' )
|
||||
_describe 'plot options' _options
|
||||
}
|
||||
|
||||
(( $+functions[_systemd-analyze_commands] )) ||
|
||||
_systemd-analyze_commands(){
|
||||
local -a _systemd_analyze_cmds
|
||||
@@ -48,7 +55,8 @@
|
||||
'time:Print time spent in the kernel before reaching userspace'
|
||||
'blame:Print list of running units ordered by time to init'
|
||||
'critical-chain:Print a tree of the time critical chain of units'
|
||||
'plot:Output SVG graphic showing service initialization'
|
||||
'plot:Output SVG graphic showing service initialization, or raw time data in
|
||||
JSON or table format'
|
||||
'dot:Dump dependency graph (in dot(1) format)'
|
||||
'dump:Dump server status'
|
||||
'cat-config:Cat systemd config files'
|
||||
@@ -97,9 +105,11 @@ _arguments \
|
||||
'--offline=[Perform a security review of the specified unit files]:BOOL:(yes no)' \
|
||||
'--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \
|
||||
'--security-policy=[Use customized requirements to compare unit files against]: PATH' \
|
||||
'--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \
|
||||
"--json=[Generate a JSON output of the security analysis table or plot's raw time data]:MODE:(pretty short off)" \
|
||||
"--table=[Generate a table of plot's raw time data]" \
|
||||
'--profile=[Include the specified profile in the security review of units]: PATH' \
|
||||
'--no-pager[Do not pipe output into a pager]' \
|
||||
"--no-legend[Do not show the headers and footers for plot's raw time data formats]" \
|
||||
'--man=[Do (not) check for existence of man pages]:BOOL:(yes no)' \
|
||||
'--generators=[Do (not) run unit generators]:BOOL:(yes no)' \
|
||||
'--order[When generating graph for dot, show only order]' \
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "analyze-time-data.h"
|
||||
#include "bus-error.h"
|
||||
#include "bus-map-properties.h"
|
||||
#include "format-table.h"
|
||||
#include "sort-util.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -37,7 +38,7 @@ typedef struct HostInfo {
|
||||
char *architecture;
|
||||
} HostInfo;
|
||||
|
||||
static HostInfo* free_host_info(HostInfo *hi) {
|
||||
static HostInfo *free_host_info(HostInfo *hi) {
|
||||
if (!hi)
|
||||
return NULL;
|
||||
|
||||
@@ -87,7 +88,7 @@ static int acquire_host_info(sd_bus *bus, HostInfo **hi) {
|
||||
}
|
||||
|
||||
r = bus_map_all_properties(
|
||||
system_bus ?: bus,
|
||||
system_bus ? : bus,
|
||||
"org.freedesktop.hostname1",
|
||||
"/org/freedesktop/hostname1",
|
||||
hostname_map,
|
||||
@@ -156,15 +157,14 @@ static void svg_graph_box(double height, double begin, double end) {
|
||||
SCALE_Y * height);
|
||||
}
|
||||
}
|
||||
|
||||
static int plot_unit_times(UnitTimes *u, double width, int y) {
|
||||
bool b;
|
||||
|
||||
if (!u->name)
|
||||
return 0;
|
||||
|
||||
svg_bar("activating", u->activating, u->activated, y);
|
||||
svg_bar("active", u->activated, u->deactivating, y);
|
||||
svg_bar("activating", u->activating, u->activated, y);
|
||||
svg_bar("active", u->activated, u->deactivating, y);
|
||||
svg_bar("deactivating", u->deactivating, u->deactivated, y);
|
||||
|
||||
/* place the text on the left if we have passed the half of the svg width */
|
||||
@@ -178,41 +178,27 @@ static int plot_unit_times(UnitTimes *u, double width, int y) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int verb_plot(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(free_host_infop) HostInfo *host = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
|
||||
_cleanup_free_ char *pretty_times = NULL;
|
||||
bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
|
||||
BootTimes *boot;
|
||||
static void limit_times_to_boot(const BootTimes *boot, UnitTimes *u) {
|
||||
if (u->deactivated > u->activating && u->deactivated <= boot->finish_time && u->activated == 0
|
||||
&& u->deactivating == 0)
|
||||
u->activated = u->deactivating = u->deactivated;
|
||||
if (u->activated < u->activating || u->activated > boot->finish_time)
|
||||
u->activated = boot->finish_time;
|
||||
if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
|
||||
u->deactivating = boot->finish_time;
|
||||
if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
|
||||
u->deactivated = boot->finish_time;
|
||||
}
|
||||
|
||||
static int produce_plot_as_svg(
|
||||
UnitTimes *times,
|
||||
const HostInfo *host,
|
||||
const BootTimes *boot,
|
||||
const char *pretty_times) {
|
||||
int m = 1, y = 0;
|
||||
UnitTimes *u;
|
||||
int n, m = 1, y = 0, r;
|
||||
double width;
|
||||
|
||||
r = acquire_bus(&bus, &use_full_bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport);
|
||||
|
||||
n = acquire_boot_times(bus, &boot);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
n = pretty_boot_time(bus, &pretty_times);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
|
||||
n = acquire_host_info(bus, &host);
|
||||
if (n < 0)
|
||||
return n;
|
||||
}
|
||||
|
||||
n = acquire_time_data(bus, ×);
|
||||
if (n <= 0)
|
||||
return n;
|
||||
|
||||
typesafe_qsort(times, n, compare_unit_start);
|
||||
|
||||
width = SCALE_X * (boot->firmware_time + boot->finish_time);
|
||||
if (width < 800.0)
|
||||
width = 800.0;
|
||||
@@ -245,16 +231,8 @@ int verb_plot(int argc, char *argv[], void *userdata) {
|
||||
if (text_width > text_start && text_width + text_start > width)
|
||||
width = text_width + text_start;
|
||||
|
||||
if (u->deactivated > u->activating &&
|
||||
u->deactivated <= boot->finish_time &&
|
||||
u->activated == 0 && u->deactivating == 0)
|
||||
u->activated = u->deactivating = u->deactivated;
|
||||
if (u->activated < u->activating || u->activated > boot->finish_time)
|
||||
u->activated = boot->finish_time;
|
||||
if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
|
||||
u->deactivating = boot->finish_time;
|
||||
if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
|
||||
u->deactivated = boot->finish_time;
|
||||
limit_times_to_boot(boot, u);
|
||||
|
||||
m++;
|
||||
}
|
||||
|
||||
@@ -391,5 +369,101 @@ int verb_plot(int argc, char *argv[], void *userdata) {
|
||||
|
||||
svg("</svg>\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int show_table(Table *table, const char *word) {
|
||||
int r;
|
||||
|
||||
assert(table);
|
||||
assert(word);
|
||||
|
||||
if (table_get_rows(table) > 1) {
|
||||
table_set_header(table, arg_legend);
|
||||
|
||||
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
|
||||
r = table_print_json(table, NULL, arg_json_format_flags | JSON_FORMAT_COLOR_AUTO);
|
||||
else
|
||||
r = table_print(table, NULL);
|
||||
if (r < 0)
|
||||
return table_log_print_error(r);
|
||||
}
|
||||
|
||||
if (arg_legend) {
|
||||
if (table_get_rows(table) > 1)
|
||||
printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
|
||||
else
|
||||
printf("No %s.\n", word);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) {
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
int r;
|
||||
|
||||
table = table_new("name", "activated", "activating", "time", "deactivated", "deactivating");
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
for (; times->has_data; times++) {
|
||||
limit_times_to_boot(boot, times);
|
||||
|
||||
r = table_add_many(
|
||||
table,
|
||||
TABLE_STRING, times->name,
|
||||
TABLE_TIMESPAN_MSEC, times->activated,
|
||||
TABLE_TIMESPAN_MSEC, times->activating,
|
||||
TABLE_TIMESPAN_MSEC, times->time,
|
||||
TABLE_TIMESPAN_MSEC, times->deactivated,
|
||||
TABLE_TIMESPAN_MSEC, times->deactivating);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
return show_table(table, "Units");
|
||||
}
|
||||
|
||||
int verb_plot(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(free_host_infop) HostInfo *host = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
|
||||
_cleanup_free_ char *pretty_times = NULL;
|
||||
bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
|
||||
BootTimes *boot;
|
||||
int n, r;
|
||||
|
||||
r = acquire_bus(&bus, &use_full_bus);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport);
|
||||
|
||||
n = acquire_boot_times(bus, &boot);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
n = pretty_boot_time(bus, &pretty_times);
|
||||
if (n < 0)
|
||||
return n;
|
||||
|
||||
if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
|
||||
n = acquire_host_info(bus, &host);
|
||||
if (n < 0)
|
||||
return n;
|
||||
}
|
||||
|
||||
n = acquire_time_data(bus, ×);
|
||||
if (n <= 0)
|
||||
return n;
|
||||
|
||||
typesafe_qsort(times, n, compare_unit_start);
|
||||
|
||||
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) || arg_table)
|
||||
r = produce_plot_as_text(times, boot);
|
||||
else
|
||||
r = produce_plot_as_svg(times, host, boot, pretty_times);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -105,6 +105,8 @@ char *arg_unit = NULL;
|
||||
JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
|
||||
bool arg_quiet = false;
|
||||
char *arg_profile = NULL;
|
||||
bool arg_legend = true;
|
||||
bool arg_table = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
|
||||
@@ -217,8 +219,10 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" --security-policy=PATH Use custom JSON security policy instead\n"
|
||||
" of built-in one\n"
|
||||
" --json=pretty|short|off Generate JSON output of the security\n"
|
||||
" analysis table\n"
|
||||
" analysis table, or plot's raw time data\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Disable column headers and hints in plot\n"
|
||||
" with either --table or --json=\n"
|
||||
" --system Operate on system systemd instance\n"
|
||||
" --user Operate on user systemd instance\n"
|
||||
" --global Operate on global user configuration\n"
|
||||
@@ -238,6 +242,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" specified time\n"
|
||||
" --profile=name|PATH Include the specified profile in the\n"
|
||||
" security review of the unit(s)\n"
|
||||
" --table Output plot's raw time data as a table\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" -q --quiet Do not emit hints\n"
|
||||
@@ -280,6 +285,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_SECURITY_POLICY,
|
||||
ARG_JSON,
|
||||
ARG_PROFILE,
|
||||
ARG_TABLE,
|
||||
ARG_NO_LEGEND,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -310,6 +317,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "unit", required_argument, NULL, 'U' },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "profile", required_argument, NULL, ARG_PROFILE },
|
||||
{ "table", optional_argument, NULL, ARG_TABLE },
|
||||
{ "no-legend", optional_argument, NULL, ARG_NO_LEGEND },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -448,14 +457,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
r = safe_atou(optarg, &arg_iterations);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse iterations: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_BASE_TIME:
|
||||
r = parse_timestamp(optarg, &arg_base_time);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
|
||||
|
||||
break;
|
||||
|
||||
case ARG_PROFILE:
|
||||
@@ -486,6 +493,15 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
free_and_replace(arg_unit, mangled);
|
||||
break;
|
||||
}
|
||||
|
||||
case ARG_TABLE:
|
||||
arg_table = true;
|
||||
break;
|
||||
|
||||
case ARG_NO_LEGEND:
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -497,9 +513,9 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option --offline= is only supported for security right now.");
|
||||
|
||||
if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
|
||||
if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Option --json= is only supported for security and inspect-elf right now.");
|
||||
"Option --json= is only supported for security, inspect-elf, and plot right now.");
|
||||
|
||||
if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
@@ -536,6 +552,16 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
|
||||
|
||||
if ((!arg_legend && !streq_ptr(argv[optind], "plot")) ||
|
||||
(streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=.");
|
||||
|
||||
if (arg_table && !streq_ptr(argv[optind], "plot"))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now.");
|
||||
|
||||
if (arg_table && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive.");
|
||||
|
||||
return 1; /* work to do */
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ extern char *arg_unit;
|
||||
extern JsonFormatFlags arg_json_format_flags;
|
||||
extern bool arg_quiet;
|
||||
extern char *arg_profile;
|
||||
extern bool arg_legend;
|
||||
extern bool arg_table;
|
||||
|
||||
int acquire_bus(sd_bus **bus, bool *use_full_bus);
|
||||
|
||||
|
||||
@@ -18,7 +18,16 @@ systemd-analyze || :
|
||||
systemd-analyze time || :
|
||||
systemd-analyze blame || :
|
||||
systemd-analyze critical-chain || :
|
||||
# plot
|
||||
systemd-analyze plot >/dev/null || :
|
||||
systemd-analyze plot --json=pretty >/dev/null || :
|
||||
systemd-analyze plot --json=short >/dev/null || :
|
||||
systemd-analyze plot --json=off >/dev/null || :
|
||||
systemd-analyze plot --json=pretty --no-legend >/dev/null || :
|
||||
systemd-analyze plot --json=short --no-legend >/dev/null || :
|
||||
systemd-analyze plot --json=off --no-legend >/dev/null || :
|
||||
systemd-analyze plot --table >/dev/null || :
|
||||
systemd-analyze plot --table --no-legend >/dev/null || :
|
||||
# legacy/deprecated options (moved to systemctl, but still usable from analyze)
|
||||
systemd-analyze log-level
|
||||
systemd-analyze log-level "$(systemctl log-level)"
|
||||
|
||||
Reference in New Issue
Block a user