/* SPDX-License-Identifier: LGPL-2.1+ */
/***
- This file is part of systemd.
-
- Copyright 2010-2013 Lennart Poettering
- Copyright 2013 Simon Peeters
+ Copyright © 2013 Simon Peeters
***/
#include <getopt.h>
+#include <inttypes.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include "conf-files.h"
#include "copy.h"
#include "fd-util.h"
+#include "fileio.h"
#include "glob-util.h"
#include "hashmap.h"
#include "locale-util.h"
#include "log.h"
+#include "main-func.h"
#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
+#include "pretty-print.h"
#if HAVE_SECCOMP
-#include "seccomp-util.h"
+# include "seccomp-util.h"
#endif
#include "special.h"
#include "strv.h"
#include "strxcpyx.h"
+#include "time-util.h"
#include "terminal-util.h"
#include "unit-name.h"
#include "util.h"
#define SCALE_X (0.1 / 1000.0) /* pixels per us */
#define SCALE_Y (20.0)
-#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
-
#define svg(...) printf(__VA_ARGS__)
#define svg_bar(class, x1, x2, y) \
static char** arg_dot_from_patterns = NULL;
static char** arg_dot_to_patterns = NULL;
static usec_t arg_fuzz = 0;
-static bool arg_no_pager = false;
+static PagerFlags arg_pager_flags = 0;
static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
static const char *arg_host = NULL;
static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
static bool arg_generators = false;
static const char *arg_root = NULL;
+STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
+
struct boot_times {
usec_t firmware_time;
usec_t loader_time;
usec_t generators_finish_time;
usec_t unitsload_start_time;
usec_t unitsload_finish_time;
+ usec_t initrd_security_start_time;
+ usec_t initrd_security_finish_time;
+ usec_t initrd_generators_start_time;
+ usec_t initrd_generators_finish_time;
+ usec_t initrd_unitsload_start_time;
+ usec_t initrd_unitsload_finish_time;
/*
* If we're analyzing the user instance, all timestamps will be offset
};
struct unit_times {
+ bool has_data;
char *name;
usec_t activating;
usec_t activated;
&error,
't', val);
- if (r < 0) {
- log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, -r));
return 0;
}
property,
&error,
strv);
- if (r < 0) {
- log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
return 0;
}
-static int compare_unit_time(const void *a, const void *b) {
- return compare(((struct unit_times *)b)->time,
- ((struct unit_times *)a)->time);
+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 void *a, const void *b) {
- return compare(((struct unit_times *)a)->activating,
- ((struct unit_times *)b)->activating);
+static int compare_unit_start(const struct unit_times *a, const struct unit_times *b) {
+ return CMP(a->activating, b->activating);
}
-static void free_unit_times(struct unit_times *t, unsigned n) {
+static void unit_times_free(struct unit_times *t) {
struct unit_times *p;
- for (p = t; p < t + n; p++)
+ for (p = t; p->has_data; p++)
free(p->name);
-
free(t);
}
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct unit_times *, unit_times_free);
+
static void subtract_timestamp(usec_t *a, usec_t b) {
assert(a);
}
static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
+ static const struct bus_properties_map property_map[] = {
+ { "FirmwareTimestampMonotonic", "t", NULL, offsetof(struct boot_times, firmware_time) },
+ { "LoaderTimestampMonotonic", "t", NULL, offsetof(struct boot_times, loader_time) },
+ { "KernelTimestamp", "t", NULL, offsetof(struct boot_times, kernel_time) },
+ { "InitRDTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_time) },
+ { "UserspaceTimestampMonotonic", "t", NULL, offsetof(struct boot_times, userspace_time) },
+ { "FinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, finish_time) },
+ { "SecurityStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, security_start_time) },
+ { "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, security_finish_time) },
+ { "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, generators_start_time) },
+ { "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, generators_finish_time) },
+ { "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, unitsload_start_time) },
+ { "UnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, unitsload_finish_time) },
+ { "InitRDSecurityStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_security_start_time) },
+ { "InitRDSecurityFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_security_finish_time) },
+ { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_generators_start_time) },
+ { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_generators_finish_time) },
+ { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_unitsload_start_time) },
+ { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(struct boot_times, initrd_unitsload_finish_time) },
+ {},
+ };
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
static struct boot_times times;
static bool cached = false;
+ int r;
if (cached)
goto finish;
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
- if (bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "FirmwareTimestampMonotonic",
- ×.firmware_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "LoaderTimestampMonotonic",
- ×.loader_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "KernelTimestamp",
- ×.kernel_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "InitRDTimestampMonotonic",
- ×.initrd_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "UserspaceTimestampMonotonic",
- ×.userspace_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "FinishTimestampMonotonic",
- ×.finish_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "SecurityStartTimestampMonotonic",
- ×.security_start_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "SecurityFinishTimestampMonotonic",
- ×.security_finish_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "GeneratorsStartTimestampMonotonic",
- ×.generators_start_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "GeneratorsFinishTimestampMonotonic",
- ×.generators_finish_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "UnitsLoadStartTimestampMonotonic",
- ×.unitsload_start_time) < 0 ||
- bus_get_uint64_property(bus,
- "/org/freedesktop/systemd1",
- "org.freedesktop.systemd1.Manager",
- "UnitsLoadFinishTimestampMonotonic",
- ×.unitsload_finish_time) < 0)
- return -EIO;
+ r = bus_map_all_properties(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ property_map,
+ BUS_MAP_STRDUP,
+ &error,
+ NULL,
+ ×);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get timestamp properties: %s", bus_error_message(&error, r));
if (times.finish_time <= 0) {
log_error("Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64").\n"
return -EINPROGRESS;
}
- if (arg_scope == UNIT_FILE_SYSTEM) {
+ if (arg_scope == UNIT_FILE_SYSTEM && times.security_start_time > 0) {
+ /* security_start_time is set when systemd is not running under container environment. */
if (times.initrd_time > 0)
times.kernel_done_time = times.initrd_time;
else
times.kernel_done_time = times.userspace_time;
} else {
/*
- * User-instance-specific timestamps processing
+ * User-instance-specific or container-system-specific timestamps processing
* (see comment to reverse_offset in struct boot_times).
*/
times.reverse_offset = times.userspace_time;
- times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0;
- subtract_timestamp(×.finish_time, times.reverse_offset);
+ times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time =
+ times.security_start_time = times.security_finish_time = 0;
- subtract_timestamp(×.security_start_time, times.reverse_offset);
- subtract_timestamp(×.security_finish_time, times.reverse_offset);
+ subtract_timestamp(×.finish_time, times.reverse_offset);
subtract_timestamp(×.generators_start_time, times.reverse_offset);
subtract_timestamp(×.generators_finish_time, times.reverse_offset);
DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info);
static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
+ static const struct bus_properties_map property_map[] = {
+ { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(struct unit_times, activating) },
+ { "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(struct unit_times, activated) },
+ { "ActiveExitTimestampMonotonic", "t", NULL, offsetof(struct unit_times, deactivating) },
+ { "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(struct unit_times, deactivated) },
+ {},
+ };
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
- int r, c = 0;
+ _cleanup_(unit_times_freep) struct unit_times *unit_times = NULL;
struct boot_times *boot_times = NULL;
- struct unit_times *unit_times = NULL;
- size_t size = 0;
+ size_t allocated = 0, c = 0;
UnitInfo u;
+ int r;
r = acquire_boot_times(bus, &boot_times);
if (r < 0)
- goto fail;
+ return r;
r = sd_bus_call_method(
bus,
"ListUnits",
&error, &reply,
NULL);
- if (r < 0) {
- log_error("Failed to list units: %s", bus_error_message(&error, -r));
- goto fail;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
- if (r < 0) {
- bus_log_parse_error(r);
- goto fail;
- }
+ if (r < 0)
+ return bus_log_parse_error(r);
while ((r = bus_parse_unit_info(reply, &u)) > 0) {
struct unit_times *t;
- if (!GREEDY_REALLOC(unit_times, size, c+1)) {
- r = log_oom();
- goto fail;
- }
+ if (!GREEDY_REALLOC(unit_times, allocated, c+2))
+ return log_oom();
- t = unit_times+c;
+ unit_times[c+1].has_data = false;
+ t = &unit_times[c];
t->name = NULL;
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
- if (bus_get_uint64_property(bus, u.unit_path,
- "org.freedesktop.systemd1.Unit",
- "InactiveExitTimestampMonotonic",
- &t->activating) < 0 ||
- bus_get_uint64_property(bus, u.unit_path,
- "org.freedesktop.systemd1.Unit",
- "ActiveEnterTimestampMonotonic",
- &t->activated) < 0 ||
- bus_get_uint64_property(bus, u.unit_path,
- "org.freedesktop.systemd1.Unit",
- "ActiveExitTimestampMonotonic",
- &t->deactivating) < 0 ||
- bus_get_uint64_property(bus, u.unit_path,
- "org.freedesktop.systemd1.Unit",
- "InactiveEnterTimestampMonotonic",
- &t->deactivated) < 0) {
- r = -EIO;
- goto fail;
- }
+ r = bus_map_all_properties(
+ bus,
+ "org.freedesktop.systemd1",
+ u.unit_path,
+ property_map,
+ BUS_MAP_STRDUP,
+ &error,
+ NULL,
+ t);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s", u.id, bus_error_message(&error, r));
subtract_timestamp(&t->activating, boot_times->reverse_offset);
subtract_timestamp(&t->activated, boot_times->reverse_offset);
continue;
t->name = strdup(u.id);
- if (!t->name) {
- r = log_oom();
- goto fail;
- }
+ if (!t->name)
+ return log_oom();
+
+ t->has_data = true;
c++;
}
- if (r < 0) {
- bus_log_parse_error(r);
- goto fail;
- }
+ if (r < 0)
+ return bus_log_parse_error(r);
- *out = unit_times;
+ *out = TAKE_PTR(unit_times);
return c;
-
-fail:
- free_unit_times(unit_times, (unsigned) c);
- return r;
}
static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
};
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *system_bus = NULL;
_cleanup_(free_host_infop) struct host_info *host;
int r;
if (!host)
return log_oom();
- r = bus_map_all_properties(bus,
+ if (arg_scope != UNIT_FILE_SYSTEM) {
+ r = bus_connect_transport(arg_transport, arg_host, false, &system_bus);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
+ goto manager;
+ }
+ }
+
+ r = bus_map_all_properties(system_bus ?: bus,
"org.freedesktop.hostname1",
"/org/freedesktop/hostname1",
hostname_map,
&error,
NULL,
host);
- if (r < 0)
- log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r));
+ if (r < 0) {
+ log_debug_errno(r, "Failed to get host information from systemd-hostnamed, ignoring: %s", bus_error_message(&error, r));
+ sd_bus_error_free(&error);
+ }
+manager:
r = bus_map_all_properties(bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"ActiveEnterTimestampMonotonic",
&activated_time);
if (r < 0) {
- log_info_errno(r, "Could not get time to reach default.target. Continuing...");
+ log_info_errno(r, "Could not get time to reach default.target, ignoring: %m");
activated_time = USEC_INFINITY;
}
size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
if (t->loader_time > 0)
size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
- if (t->kernel_time > 0)
+ if (t->kernel_done_time > 0)
size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
if (t->initrd_time > 0)
size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
- if (t->kernel_time > 0)
- strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+ if (t->kernel_done_time > 0)
+ strpcpyf(&ptr, size, "= %s ", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+
+ if (unit_id && activated_time > 0 && activated_time != USEC_INFINITY) {
+ usec_t base = t->userspace_time > 0 ? t->userspace_time : t->reverse_offset;
- if (unit_id && activated_time > 0 && activated_time != USEC_INFINITY)
- size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id, format_timespan(ts, sizeof(ts), activated_time - t->userspace_time, USEC_PER_MSEC));
- else if (unit_id && activated_time == 0)
+ size = strpcpyf(&ptr, size, "\n%s reached after %s in userspace", unit_id,
+ format_timespan(ts, sizeof(ts), activated_time - base, USEC_PER_MSEC));
+ } else if (unit_id && activated_time == 0)
size = strpcpyf(&ptr, size, "\n%s was never reached", unit_id);
else if (unit_id && activated_time == USEC_INFINITY)
- size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.",unit_id);
+ size = strpcpyf(&ptr, size, "\nCould not get time to reach %s.", unit_id);
else if (!unit_id)
size = strpcpyf(&ptr, size, "\ncould not find default.target");
}
}
+static int plot_unit_times(struct unit_times *u, double width, int y) {
+ char ts[FORMAT_TIMESPAN_MAX];
+ 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("deactivating", u->deactivating, u->deactivated, y);
+
+ /* place the text on the left if we have passed the half of the svg width */
+ b = u->activating * SCALE_X < width / 2;
+ if (u->time)
+ svg_text(b, u->activating, y, "%s (%s)",
+ u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
+ else
+ svg_text(b, u->activating, y, "%s", u->name);
+
+ return 1;
+}
+
static int analyze_plot(int argc, char *argv[], void *userdata) {
_cleanup_(free_host_infop) struct host_info *host = NULL;
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- struct unit_times *times;
+ _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+ _cleanup_free_ char *pretty_times = NULL;
+ bool use_full_bus = arg_scope == UNIT_FILE_SYSTEM;
struct boot_times *boot;
+ struct unit_times *u;
int n, m = 1, y = 0, r;
- bool use_full_bus = true;
double width;
- _cleanup_free_ char *pretty_times = NULL;
- struct unit_times *u;
r = acquire_bus(&bus, &use_full_bus);
if (r < 0)
if (n < 0)
return n;
- if (use_full_bus) {
+ if (use_full_bus || arg_scope != UNIT_FILE_SYSTEM) {
n = acquire_host_info(bus, &host);
if (n < 0)
return n;
if (n <= 0)
return n;
- qsort(times, n, sizeof(struct unit_times), compare_unit_start);
+ typesafe_qsort(times, n, compare_unit_start);
width = SCALE_X * (boot->firmware_time + boot->finish_time);
if (width < 800.0)
}
if (boot->initrd_time > 0)
m++;
- if (boot->kernel_time > 0)
+ if (boot->kernel_done_time > 0)
m++;
- for (u = times; u < times + n; u++) {
+ for (u = times; u->has_data; u++) {
double text_start, text_width;
- if (u->activating < boot->userspace_time ||
- u->activating > boot->finish_time) {
+ if (u->activating > boot->finish_time) {
u->name = mfree(u->name);
continue;
}
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)
+ 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->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;
svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
- if (use_full_bus)
+ if (host)
svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
strempty(host->hostname),
svg_text(true, -(double) boot->loader_time, y, "loader");
y++;
}
- if (boot->kernel_time > 0) {
+ if (boot->kernel_done_time > 0) {
svg_bar("kernel", 0, boot->kernel_done_time, y);
svg_text(true, 0, y, "kernel");
y++;
}
if (boot->initrd_time > 0) {
svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
+ if (boot->initrd_security_start_time < boot->initrd_security_finish_time)
+ svg_bar("security", boot->initrd_security_start_time, boot->initrd_security_finish_time, y);
+ if (boot->initrd_generators_start_time < boot->initrd_generators_finish_time)
+ svg_bar("generators", boot->initrd_generators_start_time, boot->initrd_generators_finish_time, y);
+ if (boot->initrd_unitsload_start_time < boot->initrd_unitsload_finish_time)
+ svg_bar("unitsload", boot->initrd_unitsload_start_time, boot->initrd_unitsload_finish_time, y);
svg_text(true, boot->initrd_time, y, "initrd");
y++;
}
+
+ for (u = times; u->has_data; u++) {
+ if (u->activating >= boot->userspace_time)
+ break;
+
+ y += plot_unit_times(u, width, y);
+ }
+
svg_bar("active", boot->userspace_time, boot->finish_time, y);
- svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
+ if (boot->security_start_time > 0)
+ svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
svg_text(true, boot->userspace_time, y, "systemd");
y++;
- for (u = times; u < times + n; u++) {
- char ts[FORMAT_TIMESPAN_MAX];
- bool b;
-
- if (!u->name)
- continue;
-
- 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 */
- b = u->activating * SCALE_X < width / 2;
- if (u->time)
- svg_text(b, u->activating, y, "%s (%s)",
- u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
- else
- svg_text(b, u->activating, y, "%s", u->name);
- y++;
- }
+ for (; u->has_data; u++)
+ y += plot_unit_times(u, width, y);
svg("</g>\n");
svg_bar("deactivating", 0, 300000, y);
svg_text(true, 400000, y, "Deactivating");
y++;
- svg_bar("security", 0, 300000, y);
- svg_text(true, 400000, y, "Setting up security module");
- y++;
+ if (boot->security_start_time > 0) {
+ svg_bar("security", 0, 300000, y);
+ svg_text(true, 400000, y, "Setting up security module");
+ y++;
+ }
svg_bar("generators", 0, 300000, y);
svg_text(true, 400000, y, "Generators");
y++;
svg("</svg>\n");
- free_unit_times(times, (unsigned) n);
-
- n = 0;
- return n;
+ return 0;
}
-static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
+static int list_dependencies_print(const char *name, unsigned level, unsigned branches,
bool last, struct unit_times *times, struct boot_times *boot) {
- unsigned int i;
+ unsigned i;
char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
for (i = level; i != 0; i--)
static Hashmap *unit_times_hashmap;
-static int list_dependencies_compare(const void *_a, const void *_b) {
- const char **a = (const char**) _a, **b = (const char**) _b;
+static int list_dependencies_compare(char * const *a, char * const *b) {
usec_t usa = 0, usb = 0;
struct unit_times *times;
if (times)
usb = times->activated;
- return usb - usa;
+ return CMP(usb, usa);
}
static bool times_in_range(const struct unit_times *times, const struct boot_times *boot) {
times->activated > 0 && times->activated <= boot->finish_time;
}
-static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
- unsigned int branches) {
+static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units,
+ unsigned branches) {
_cleanup_strv_free_ char **deps = NULL;
char **c;
int r = 0;
if (r < 0)
return r;
- qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
+ typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
r = acquire_boot_times(bus, &boot);
if (r < 0)
STRV_FOREACH(c, deps) {
times = hashmap_get(unit_times_hashmap, *c);
if (times_in_range(times, boot) &&
- (times->activated >= service_longest
- || service_longest == 0)) {
+ times->activated >= service_longest)
service_longest = times->activated;
- break;
- }
}
if (service_longest == 0)
&error,
&reply,
"s");
- if (r < 0) {
- log_error("Failed to get ID: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, -r));
r = sd_bus_message_read(reply, "s", &id);
if (r < 0)
static int analyze_critical_chain(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- struct unit_times *times;
- unsigned int i;
+ _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+ struct unit_times *u;
Hashmap *h;
int n, r;
if (!h)
return log_oom();
- for (i = 0; i < (unsigned) n; i++) {
- r = hashmap_put(h, times[i].name, ×[i]);
+ for (u = times; u->has_data; u++) {
+ r = hashmap_put(h, u->name, u);
if (r < 0)
return log_error_errno(r, "Failed to add entry to hashmap: %m");
}
unit_times_hashmap = h;
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
puts("The time after the unit is active or started is printed after the \"@\" character.\n"
"The time the unit takes to start is printed after the \"+\" character.\n");
list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
h = hashmap_free(h);
- free_unit_times(times, (unsigned) n);
return 0;
}
static int analyze_blame(int argc, char *argv[], void *userdata) {
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
- struct unit_times *times;
- unsigned i;
+ _cleanup_(unit_times_freep) struct unit_times *times = NULL;
+ struct unit_times *u;
int n, r;
r = acquire_bus(&bus, NULL);
if (n <= 0)
return n;
- qsort(times, n, sizeof(struct unit_times), compare_unit_time);
+ typesafe_qsort(times, n, compare_unit_time);
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
- for (i = 0; i < (unsigned) n; i++) {
+ for (u = times; u->has_data; u++) {
char ts[FORMAT_TIMESPAN_MAX];
- if (times[i].time > 0)
- printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
+ if (u->time > 0)
+ printf("%16s %s\n", format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC), u->name);
}
- free_unit_times(times, (unsigned) n);
return 0;
}
&error,
&reply,
"");
- if (r < 0) {
- log_error("Failed to list units: %s", bus_error_message(&error, -r));
- return r;
- }
+ if (r < 0)
+ log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
if (r < 0)
return log_error_errno(r, "Failed to create bus connection: %m");
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
if (!sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD))
return dump_fallback(bus);
char **arg;
int r;
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
STRV_FOREACH(arg, argv + 1) {
const char *t = NULL;
break;
}
- if (!t) {
- log_error("Path %s does not start with any known prefix.", *arg);
- return -EINVAL;
- }
+ if (!t)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Path %s does not start with any known prefix.",
+ *arg);
} else
t = *arg;
}
#if HAVE_SECCOMP
+
+static int load_kernel_syscalls(Set **ret) {
+ _cleanup_(set_free_freep) Set *syscalls = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ /* Let's read the available system calls from the list of available tracing events. Slightly dirty, but good
+ * enough for analysis purposes. */
+
+ f = fopen("/sys/kernel/debug/tracing/available_events", "re");
+ if (!f)
+ return log_full_errno(IN_SET(errno, EPERM, EACCES, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno, "Can't read open /sys/kernel/debug/tracing/available_events: %m");
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+ const char *e;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read system call list: %m");
+ if (r == 0)
+ break;
+
+ e = startswith(line, "syscalls:sys_enter_");
+ if (!e)
+ continue;
+
+ /* These are named differently inside the kernel than their external name for historical reasons. Let's hide them here. */
+ if (STR_IN_SET(e, "newuname", "newfstat", "newstat", "newlstat", "sysctl"))
+ continue;
+
+ r = set_ensure_allocated(&syscalls, &string_hash_ops);
+ if (r < 0)
+ return log_oom();
+
+ r = set_put_strdup(syscalls, e);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add system call to list: %m");
+ }
+
+ *ret = TAKE_PTR(syscalls);
+ return 0;
+}
+
+static void kernel_syscalls_remove(Set *s, const SyscallFilterSet *set) {
+ const char *syscall;
+
+ NULSTR_FOREACH(syscall, set->value) {
+ if (syscall[0] == '@')
+ continue;
+
+ (void) set_remove(s, syscall);
+ }
+}
+
static void dump_syscall_filter(const SyscallFilterSet *set) {
const char *syscall;
- printf("%s\n", set->name);
- printf(" # %s\n", set->help);
+ printf("%s%s%s\n"
+ " # %s\n",
+ ansi_highlight(),
+ set->name,
+ ansi_normal(),
+ set->help);
+
NULSTR_FOREACH(syscall, set->value)
- printf(" %s\n", syscall);
+ printf(" %s%s%s\n",
+ syscall[0] == '@' ? ansi_underline() : "",
+ syscall,
+ ansi_normal());
}
static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
bool first = true;
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
if (strv_isempty(strv_skip(argv, 1))) {
- int i;
+ _cleanup_(set_free_freep) Set *kernel = NULL;
+ int i, k;
+
+ k = load_kernel_syscalls(&kernel);
for (i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) {
+ const SyscallFilterSet *set = syscall_filter_sets + i;
if (!first)
puts("");
- dump_syscall_filter(syscall_filter_sets + i);
+
+ dump_syscall_filter(set);
+ kernel_syscalls_remove(kernel, set);
first = false;
}
+
+ if (k < 0) {
+ fputc('\n', stdout);
+ fflush(stdout);
+ log_notice_errno(k, "# Not showing unlisted system calls, couldn't retrieve kernel system call list: %m");
+ } else if (!set_isempty(kernel)) {
+ const char *syscall;
+ Iterator j;
+
+ printf("\n"
+ "# %sUnlisted System Calls%s (supported by the local kernel, but not included in any of the groups listed above):\n",
+ ansi_highlight(), ansi_normal());
+
+ SET_FOREACH(syscall, kernel, j)
+ printf("# %s\n", syscall);
+ }
} else {
char **name;
}
#else
-static int dump_syscall_filters(int argc, char *argv[], void *userdata) {
- log_error("Not compiled with syscall filters, sorry.");
- return -EOPNOTSUPP;
-}
+static int dump_syscall_filters(int argc, char *argv[], void *userdata)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Not compiled with syscall filters, sorry.");
#endif
+static int dump_timespan(int argc, char *argv[], void *userdata) {
+ char **input_timespan;
+
+ STRV_FOREACH(input_timespan, strv_skip(argv, 1)) {
+ 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)
+ return log_error_errno(r, "Failed to parse time span '%s': %m", *input_timespan);
+
+ printf("Original: %s\n", *input_timespan);
+ printf(" %ss: %" PRIu64 "\n", special_glyph(MU), output_usecs);
+ printf(" Human: %s\n", format_timespan(ft_buf, sizeof(ft_buf), output_usecs, usec_magnitude));
+
+ if (input_timespan[1])
+ putchar('\n');
+ }
+
+ return EXIT_SUCCESS;
+}
+
static int test_calendar(int argc, char *argv[], void *userdata) {
int ret = 0, r;
char **p;
}
static int help(int argc, char *argv[], void *userdata) {
+ _cleanup_free_ char *link = NULL;
+ int r;
- (void) pager_open(arg_no_pager, false);
+ (void) pager_open(arg_pager_flags);
+
+ r = terminal_urlify_man("systemd-analyze", "1", &link);
+ if (r < 0)
+ return log_oom();
printf("%s [OPTIONS...] {COMMAND} ...\n\n"
"Profile systemd, show unit dependencies, check unit files.\n\n"
" --to-pattern=GLOB Show only destinations in the graph\n"
" --fuzz=SECONDS Also print also services which finished SECONDS\n"
" earlier than the latest in the branch\n"
- " --man[=BOOL] Do [not] check for existence of man pages\n\n"
- " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n\n"
- "Commands:\n"
+ " --man[=BOOL] Do [not] check for existence of man pages\n"
+ " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n"
+ "\nCommands:\n"
" time Print time spent in the kernel\n"
" blame Print list of running units ordered by time to init\n"
" critical-chain [UNIT...] Print a tree of the time critical chain of units\n"
" verify FILE... Check unit files for correctness\n"
" calendar SPEC... Validate repetitive calendar time events\n"
" service-watchdogs [BOOL] Get/set service watchdog state\n"
- , program_invocation_short_name);
+ " timespan SPAN... Validate a time span\n"
+ "\nSee the %s for details.\n"
+ , program_invocation_short_name
+ , link
+ );
- /* When updating this list, including descriptions, apply
- * changes to shell-completion/bash/systemd-analyze and
+ /* When updating this list, including descriptions, apply changes to shell-completion/bash/systemd-analyze and
* shell-completion/zsh/_systemd-analyze too. */
return 0;
break;
case ARG_NO_PAGER:
- arg_no_pager = true;
+ arg_pager_flags |= PAGER_DISABLE;
break;
case 'H':
case ARG_MAN:
if (optarg) {
r = parse_boolean(optarg);
- if (r < 0) {
- log_error("Failed to parse --man= argument.");
- return -EINVAL;
- }
+ if (r < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse --man= argument.");
- arg_man = !!r;
+ arg_man = r;
} else
arg_man = true;
case ARG_GENERATORS:
if (optarg) {
r = parse_boolean(optarg);
- if (r < 0) {
- log_error("Failed to parse --generators= argument.");
- return -EINVAL;
- }
+ if (r < 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to parse --generators= argument.");
- arg_generators = !!r;
+ arg_generators = r;
} else
arg_generators = true;
}
if (arg_scope == UNIT_FILE_GLOBAL &&
- !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify")) {
- log_error("Option --global only makes sense with verbs dot, unit-paths, verify.");
- return -EINVAL;
- }
+ !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --global only makes sense with verbs dot, unit-paths, verify.");
- if (arg_root && !streq_ptr(argv[optind], "cat-config")) {
- log_error("Option --root is only supported for cat-config right now.");
- return -EINVAL;
- }
+ if (arg_root && !streq_ptr(argv[optind], "cat-config"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Option --root is only supported for cat-config right now.");
return 1; /* work to do */
}
-int main(int argc, char *argv[]) {
+static int run(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "verify", 2, VERB_ANY, 0, do_verify },
{ "calendar", 2, VERB_ANY, 0, test_calendar },
{ "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
+ { "timespan", 2, VERB_ANY, 0, dump_timespan },
{}
};
r = parse_argv(argc, argv);
if (r <= 0)
- goto finish;
-
- r = dispatch_verb(argc, argv, verbs, NULL);
-
-finish:
- pager_close();
-
- strv_free(arg_dot_from_patterns);
- strv_free(arg_dot_to_patterns);
+ return r;
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ return dispatch_verb(argc, argv, verbs, NULL);
}
+
+DEFINE_MAIN_FUNCTION(run);