summaryrefslogtreecommitdiff
path: root/src/grp-system/grp-utils/systemd-analyze
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-system/grp-utils/systemd-analyze')
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/.gitignore1
l---------src/grp-system/grp-utils/systemd-analyze/GNUmakefile1
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/Makefile39
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/analyze-verify.c316
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/analyze-verify.h26
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/analyze.c1486
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash117
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh58
-rw-r--r--src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml389
9 files changed, 2433 insertions, 0 deletions
diff --git a/src/grp-system/grp-utils/systemd-analyze/.gitignore b/src/grp-system/grp-utils/systemd-analyze/.gitignore
new file mode 100644
index 0000000000..752ea236c8
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/.gitignore
@@ -0,0 +1 @@
+/systemd-analyze
diff --git a/src/grp-system/grp-utils/systemd-analyze/GNUmakefile b/src/grp-system/grp-utils/systemd-analyze/GNUmakefile
new file mode 120000
index 0000000000..13308a50cd
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/GNUmakefile
@@ -0,0 +1 @@
+../../../../GNUmakefile \ No newline at end of file
diff --git a/src/grp-system/grp-utils/systemd-analyze/Makefile b/src/grp-system/grp-utils/systemd-analyze/Makefile
new file mode 100644
index 0000000000..cb1b72e77d
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/Makefile
@@ -0,0 +1,39 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+bin_PROGRAMS += systemd-analyze
+systemd_analyze_SOURCES = \
+ src/analyze/analyze.c \
+ src/analyze/analyze-verify.c \
+ src/analyze/analyze-verify.h
+
+systemd_analyze_CFLAGS = \
+ $(SECCOMP_CFLAGS) \
+ $(MOUNT_CFLAGS)
+
+systemd_analyze_LDADD = \
+ libcore.la
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c
new file mode 100644
index 0000000000..22d210a14b
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.c
@@ -0,0 +1,316 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdlib.h>
+
+#include "core/manager.h"
+#include "sd-bus/bus-error.h"
+#include "sd-bus/bus-util.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/log.h"
+#include "systemd-basic/path-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/unit-name.h"
+#include "systemd-shared/pager.h"
+
+#include "analyze-verify.h"
+
+static int prepare_filename(const char *filename, char **ret) {
+ int r;
+ const char *name;
+ _cleanup_free_ char *abspath = NULL;
+ _cleanup_free_ char *dir = NULL;
+ _cleanup_free_ char *with_instance = NULL;
+ char *c;
+
+ assert(filename);
+ assert(ret);
+
+ r = path_make_absolute_cwd(filename, &abspath);
+ if (r < 0)
+ return r;
+
+ name = basename(abspath);
+ if (!unit_name_is_valid(name, UNIT_NAME_ANY))
+ return -EINVAL;
+
+ if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) {
+ r = unit_name_replace_instance(name, "i", &with_instance);
+ if (r < 0)
+ return r;
+ }
+
+ dir = dirname_malloc(abspath);
+ if (!dir)
+ return -ENOMEM;
+
+ if (with_instance)
+ c = path_join(NULL, dir, with_instance);
+ else
+ c = path_join(NULL, dir, name);
+ if (!c)
+ return -ENOMEM;
+
+ *ret = c;
+ return 0;
+}
+
+static int generate_path(char **var, char **filenames) {
+ const char *old;
+ char **filename;
+
+ _cleanup_strv_free_ char **ans = NULL;
+ int r;
+
+ STRV_FOREACH(filename, filenames) {
+ char *t;
+
+ t = dirname_malloc(*filename);
+ if (!t)
+ return -ENOMEM;
+
+ r = strv_consume(&ans, t);
+ if (r < 0)
+ return r;
+ }
+
+ assert_se(strv_uniq(ans));
+
+ /* First, prepend our directories. Second, if some path was specified, use that, and
+ * otherwise use the defaults. Any duplicates will be filtered out in path-lookup.c.
+ * Treat explicit empty path to mean that nothing should be appended.
+ */
+ old = getenv("SYSTEMD_UNIT_PATH");
+ if (!streq_ptr(old, "")) {
+ if (!old)
+ old = ":";
+
+ r = strv_extend(&ans, old);
+ if (r < 0)
+ return r;
+ }
+
+ *var = strv_join(ans, ":");
+ if (!*var)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int verify_socket(Unit *u) {
+ int r;
+
+ assert(u);
+
+ if (u->type != UNIT_SOCKET)
+ return 0;
+
+ /* Cannot run this without the service being around */
+
+ /* This makes sure instance is created if necessary. */
+ r = socket_instantiate_service(SOCKET(u));
+ if (r < 0) {
+ log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m");
+ return r;
+ }
+
+ /* This checks both type of sockets */
+ if (UNIT_ISSET(SOCKET(u)->service)) {
+ Service *service;
+
+ service = SERVICE(UNIT_DEREF(SOCKET(u)->service));
+ log_unit_debug(u, "Using %s", UNIT(service)->id);
+
+ if (UNIT(service)->load_state != UNIT_LOADED) {
+ log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+static int verify_executable(Unit *u, ExecCommand *exec) {
+ if (exec == NULL)
+ return 0;
+
+ if (access(exec->path, X_OK) < 0)
+ return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path);
+
+ return 0;
+}
+
+static int verify_executables(Unit *u) {
+ ExecCommand *exec;
+ int r = 0, k;
+ unsigned i;
+
+ assert(u);
+
+ exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command :
+ u->type == UNIT_MOUNT ? MOUNT(u)->control_command :
+ u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL;
+ k = verify_executable(u, exec);
+ if (k < 0 && r == 0)
+ r = k;
+
+ if (u->type == UNIT_SERVICE)
+ for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) {
+ k = verify_executable(u, SERVICE(u)->exec_command[i]);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ if (u->type == UNIT_SOCKET)
+ for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) {
+ k = verify_executable(u, SOCKET(u)->exec_command[i]);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+ return r;
+}
+
+static int verify_documentation(Unit *u, bool check_man) {
+ char **p;
+ int r = 0, k;
+
+ STRV_FOREACH(p, u->documentation) {
+ log_unit_debug(u, "Found documentation item: %s", *p);
+
+ if (check_man && startswith(*p, "man:")) {
+ k = show_man_page(*p + 4, true);
+ if (k != 0) {
+ if (k < 0)
+ log_unit_error_errno(u, r, "Can't show %s: %m", *p);
+ else {
+ log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k);
+ k = -ENOEXEC;
+ }
+ if (r == 0)
+ r = k;
+ }
+ }
+ }
+
+ /* Check remote URLs? */
+
+ return r;
+}
+
+static int verify_unit(Unit *u, bool check_man) {
+ _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
+ int r, k;
+
+ assert(u);
+
+ if (log_get_max_level() >= LOG_DEBUG)
+ unit_dump(u, stdout, "\t");
+
+ log_unit_debug(u, "Creating %s/start job", u->id);
+ r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL);
+ if (r < 0)
+ log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r));
+
+ k = verify_socket(u);
+ if (k < 0 && r == 0)
+ r = k;
+
+ k = verify_executables(u);
+ if (k < 0 && r == 0)
+ r = k;
+
+ k = verify_documentation(u, check_man);
+ if (k < 0 && r == 0)
+ r = k;
+
+ return r;
+}
+
+int verify_units(char **filenames, UnitFileScope scope, bool check_man) {
+ _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *var = NULL;
+ Manager *m = NULL;
+ FILE *serial = NULL;
+ FDSet *fdset = NULL;
+ char **filename;
+ int r = 0, k;
+
+ Unit *units[strv_length(filenames)];
+ int i, count = 0;
+
+ if (strv_isempty(filenames))
+ return 0;
+
+ /* set the path */
+ r = generate_path(&var, filenames);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate unit load path: %m");
+
+ assert_se(set_unit_path(var) >= 0);
+
+ r = manager_new(scope, true, &m);
+ if (r < 0)
+ return log_error_errno(r, "Failed to initialize manager: %m");
+
+ log_debug("Starting manager...");
+
+ r = manager_startup(m, serial, fdset);
+ if (r < 0) {
+ log_error_errno(r, "Failed to start manager: %m");
+ goto finish;
+ }
+
+ manager_clear_jobs(m);
+
+ log_debug("Loading remaining units from the command line...");
+
+ STRV_FOREACH(filename, filenames) {
+ _cleanup_free_ char *prepared = NULL;
+
+ log_debug("Handling %s...", *filename);
+
+ k = prepare_filename(*filename, &prepared);
+ if (k < 0) {
+ log_error_errno(k, "Failed to prepare filename %s: %m", *filename);
+ if (r == 0)
+ r = k;
+ continue;
+ }
+
+ k = manager_load_unit(m, NULL, prepared, &err, &units[count]);
+ if (k < 0) {
+ log_error_errno(k, "Failed to load %s: %m", *filename);
+ if (r == 0)
+ r = k;
+ } else
+ count++;
+ }
+
+ for (i = 0; i < count; i++) {
+ k = verify_unit(units[i], check_man);
+ if (k < 0 && r == 0)
+ r = k;
+ }
+
+finish:
+ manager_free(m);
+
+ return r;
+}
diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h
new file mode 100644
index 0000000000..33b809997a
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/analyze-verify.h
@@ -0,0 +1,26 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+#include "systemd-shared/path-lookup.h"
+
+int verify_units(char **filenames, UnitFileScope scope, bool check_man);
diff --git a/src/grp-system/grp-utils/systemd-analyze/analyze.c b/src/grp-system/grp-utils/systemd-analyze/analyze.c
new file mode 100644
index 0000000000..ea775c8dd1
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/analyze.c
@@ -0,0 +1,1486 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2010-2013 Lennart Poettering
+ Copyright 2013 Simon Peeters
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <getopt.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <systemd/sd-bus.h>
+
+#include "sd-bus/bus-error.h"
+#include "sd-bus/bus-util.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/glob-util.h"
+#include "systemd-basic/hashmap.h"
+#include "systemd-basic/locale-util.h"
+#include "systemd-basic/log.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/special.h"
+#include "systemd-basic/strv.h"
+#include "systemd-basic/strxcpyx.h"
+#include "systemd-basic/terminal-util.h"
+#include "systemd-basic/unit-name.h"
+#include "systemd-basic/util.h"
+#include "systemd-shared/bus-unit-util.h"
+#include "systemd-shared/pager.h"
+
+#include "analyze-verify.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) \
+ svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
+ (class), \
+ SCALE_X * (x1), SCALE_Y * (y), \
+ SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
+
+#define svg_text(b, x, y, format, ...) \
+ do { \
+ svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
+ svg(format, ## __VA_ARGS__); \
+ svg("</text>\n"); \
+ } while (false)
+
+static enum dot {
+ DEP_ALL,
+ DEP_ORDER,
+ DEP_REQUIRE
+} arg_dot = DEP_ALL;
+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 BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
+static char *arg_host = NULL;
+static bool arg_user = false;
+static bool arg_man = true;
+
+struct boot_times {
+ usec_t firmware_time;
+ usec_t loader_time;
+ usec_t kernel_time;
+ usec_t kernel_done_time;
+ usec_t initrd_time;
+ usec_t userspace_time;
+ usec_t finish_time;
+ usec_t security_start_time;
+ usec_t security_finish_time;
+ usec_t generators_start_time;
+ usec_t generators_finish_time;
+ usec_t unitsload_start_time;
+ usec_t unitsload_finish_time;
+
+ /*
+ * If we're analyzing the user instance, all timestamps will be offset
+ * by its own start-up timestamp, which may be arbitrarily big.
+ * With "plot", this causes arbitrarily wide output SVG files which almost
+ * completely consist of empty space. Thus we cancel out this offset.
+ *
+ * This offset is subtracted from times above by acquire_boot_times(),
+ * but it still needs to be subtracted from unit-specific timestamps
+ * (so it is stored here for reference).
+ */
+ usec_t reverse_offset;
+};
+
+struct unit_times {
+ char *name;
+ usec_t activating;
+ usec_t activated;
+ usec_t deactivated;
+ usec_t deactivating;
+ usec_t time;
+};
+
+struct host_info {
+ char *hostname;
+ char *kernel_name;
+ char *kernel_release;
+ char *kernel_version;
+ char *os_pretty_name;
+ char *virtualization;
+ char *architecture;
+};
+
+static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(interface);
+ assert(property);
+ assert(val);
+
+ r = sd_bus_get_property_trivial(
+ bus,
+ "org.freedesktop.systemd1",
+ path,
+ interface,
+ property,
+ &error,
+ 't', val);
+
+ if (r < 0) {
+ log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ return 0;
+}
+
+static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(path);
+ assert(property);
+ assert(strv);
+
+ r = sd_bus_get_property_strv(
+ bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit",
+ property,
+ &error,
+ strv);
+ if (r < 0) {
+ log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
+ return 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_start(const void *a, const void *b) {
+ return compare(((struct unit_times *)a)->activating,
+ ((struct unit_times *)b)->activating);
+}
+
+static void free_unit_times(struct unit_times *t, unsigned n) {
+ struct unit_times *p;
+
+ for (p = t; p < t + n; p++)
+ free(p->name);
+
+ free(t);
+}
+
+static void subtract_timestamp(usec_t *a, usec_t b) {
+ assert(a);
+
+ if (*a > 0) {
+ assert(*a >= b);
+ *a -= b;
+ }
+}
+
+static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
+ static struct boot_times times;
+ static bool cached = false;
+
+ 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",
+ &times.firmware_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LoaderTimestampMonotonic",
+ &times.loader_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "KernelTimestamp",
+ &times.kernel_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "InitRDTimestampMonotonic",
+ &times.initrd_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "UserspaceTimestampMonotonic",
+ &times.userspace_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "FinishTimestampMonotonic",
+ &times.finish_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "SecurityStartTimestampMonotonic",
+ &times.security_start_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "SecurityFinishTimestampMonotonic",
+ &times.security_finish_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "GeneratorsStartTimestampMonotonic",
+ &times.generators_start_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "GeneratorsFinishTimestampMonotonic",
+ &times.generators_finish_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "UnitsLoadStartTimestampMonotonic",
+ &times.unitsload_start_time) < 0 ||
+ bus_get_uint64_property(bus,
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "UnitsLoadFinishTimestampMonotonic",
+ &times.unitsload_finish_time) < 0)
+ return -EIO;
+
+ if (times.finish_time <= 0) {
+ log_error("Bootup is not yet finished. Please try again later.");
+ return -EINPROGRESS;
+ }
+
+ if (arg_user) {
+ /*
+ * User-instance-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(&times.finish_time, times.reverse_offset);
+
+ subtract_timestamp(&times.security_start_time, times.reverse_offset);
+ subtract_timestamp(&times.security_finish_time, times.reverse_offset);
+
+ subtract_timestamp(&times.generators_start_time, times.reverse_offset);
+ subtract_timestamp(&times.generators_finish_time, times.reverse_offset);
+
+ subtract_timestamp(&times.unitsload_start_time, times.reverse_offset);
+ subtract_timestamp(&times.unitsload_finish_time, times.reverse_offset);
+ } else {
+ if (times.initrd_time)
+ times.kernel_done_time = times.initrd_time;
+ else
+ times.kernel_done_time = times.userspace_time;
+ }
+
+ cached = true;
+
+finish:
+ *bt = &times;
+ return 0;
+}
+
+static void free_host_info(struct host_info *hi) {
+
+ if (!hi)
+ return;
+
+ free(hi->hostname);
+ free(hi->kernel_name);
+ free(hi->kernel_release);
+ free(hi->kernel_version);
+ free(hi->os_pretty_name);
+ free(hi->virtualization);
+ free(hi->architecture);
+ free(hi);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info);
+
+static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
+ _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;
+ struct boot_times *boot_times = NULL;
+ struct unit_times *unit_times = NULL;
+ size_t size = 0;
+ UnitInfo u;
+
+ r = acquire_boot_times(bus, &boot_times);
+ if (r < 0)
+ goto fail;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "ListUnits",
+ &error, &reply,
+ NULL);
+ if (r < 0) {
+ log_error("Failed to list units: %s", bus_error_message(&error, -r));
+ goto fail;
+ }
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
+ if (r < 0) {
+ bus_log_parse_error(r);
+ goto fail;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ subtract_timestamp(&t->activating, boot_times->reverse_offset);
+ subtract_timestamp(&t->activated, boot_times->reverse_offset);
+ subtract_timestamp(&t->deactivating, boot_times->reverse_offset);
+ subtract_timestamp(&t->deactivated, boot_times->reverse_offset);
+
+ if (t->activated >= t->activating)
+ t->time = t->activated - t->activating;
+ else if (t->deactivated >= t->activating)
+ t->time = t->deactivated - t->activating;
+ else
+ t->time = 0;
+
+ if (t->activating == 0)
+ continue;
+
+ t->name = strdup(u.id);
+ if (t->name == NULL) {
+ r = log_oom();
+ goto fail;
+ }
+ c++;
+ }
+ if (r < 0) {
+ bus_log_parse_error(r);
+ goto fail;
+ }
+
+ *out = unit_times;
+ return c;
+
+fail:
+ if (unit_times)
+ free_unit_times(unit_times, (unsigned) c);
+ return r;
+}
+
+static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
+ static const struct bus_properties_map hostname_map[] = {
+ { "Hostname", "s", NULL, offsetof(struct host_info, hostname) },
+ { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) },
+ { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) },
+ { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) },
+ { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) },
+ {}
+ };
+
+ static const struct bus_properties_map manager_map[] = {
+ { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) },
+ { "Architecture", "s", NULL, offsetof(struct host_info, architecture) },
+ {}
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(free_host_infop) struct host_info *host;
+ int r;
+
+ host = new0(struct host_info, 1);
+ if (!host)
+ return log_oom();
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ hostname_map,
+ host);
+ if (r < 0)
+ log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r));
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ manager_map,
+ host);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r));
+
+ *hi = host;
+ host = NULL;
+
+ return 0;
+}
+
+static int pretty_boot_time(sd_bus *bus, char **_buf) {
+ char ts[FORMAT_TIMESPAN_MAX];
+ struct boot_times *t;
+ static char buf[4096];
+ size_t size;
+ char *ptr;
+ int r;
+
+ r = acquire_boot_times(bus, &t);
+ if (r < 0)
+ return r;
+
+ ptr = buf;
+ size = sizeof(buf);
+
+ size = strpcpyf(&ptr, size, "Startup finished in ");
+ if (t->firmware_time)
+ size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
+ if (t->loader_time)
+ size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
+ if (t->kernel_time)
+ 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));
+ strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
+
+ ptr = strdup(buf);
+ if (!ptr)
+ return log_oom();
+
+ *_buf = ptr;
+ return 0;
+}
+
+static void svg_graph_box(double height, double begin, double end) {
+ long long i;
+
+ /* outside box, fill */
+ svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
+ SCALE_X * (end - begin), SCALE_Y * height);
+
+ for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
+ /* lines for each second */
+ if (i % 5000000 == 0)
+ svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
+ " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
+ SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+ else if (i % 1000000 == 0)
+ svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
+ " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
+ SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
+ else
+ svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
+ SCALE_X * i, SCALE_X * i, SCALE_Y * height);
+ }
+}
+
+static int analyze_plot(sd_bus *bus) {
+ _cleanup_(free_host_infop) struct host_info *host = NULL;
+ struct unit_times *times;
+ struct boot_times *boot;
+ int n, m = 1, y=0;
+ double width;
+ _cleanup_free_ char *pretty_times = NULL;
+ struct unit_times *u;
+
+ n = acquire_boot_times(bus, &boot);
+ if (n < 0)
+ return n;
+
+ n = pretty_boot_time(bus, &pretty_times);
+ if (n < 0)
+ return n;
+
+ n = acquire_host_info(bus, &host);
+ if (n < 0)
+ return n;
+
+ n = acquire_time_data(bus, &times);
+ if (n <= 0)
+ return n;
+
+ qsort(times, n, sizeof(struct unit_times), compare_unit_start);
+
+ width = SCALE_X * (boot->firmware_time + boot->finish_time);
+ if (width < 800.0)
+ width = 800.0;
+
+ if (boot->firmware_time > boot->loader_time)
+ m++;
+ if (boot->loader_time) {
+ m++;
+ if (width < 1000.0)
+ width = 1000.0;
+ }
+ if (boot->initrd_time)
+ m++;
+ if (boot->kernel_time)
+ m++;
+
+ for (u = times; u < times + n; u++) {
+ double text_start, text_width;
+
+ if (u->activating < boot->userspace_time ||
+ u->activating > boot->finish_time) {
+ u->name = mfree(u->name);
+ continue;
+ }
+
+ /* If the text cannot fit on the left side then
+ * increase the svg width so it fits on the right.
+ * TODO: calculate the text width more accurately */
+ text_width = 8.0 * strlen(u->name);
+ text_start = (boot->firmware_time + u->activating) * SCALE_X;
+ 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->activated > boot->finish_time)
+ u->deactivating = boot->finish_time;
+ if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
+ u->deactivated = boot->finish_time;
+ m++;
+ }
+
+ svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
+ "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+
+ svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
+ "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
+ 80.0 + width, 150.0 + (m * SCALE_Y) +
+ 5 * SCALE_Y /* legend */);
+
+ /* write some basic info as a comment, including some help */
+ svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
+ "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
+ "<!-- that render these files properly but much slower are ImageMagick, -->\n"
+ "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
+ "<!-- point your browser to this file. -->\n\n"
+ "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
+
+ /* style sheet */
+ svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
+ " rect { stroke-width: 1; stroke-opacity: 0; }\n"
+ " rect.background { fill: rgb(255,255,255); }\n"
+ " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
+ " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
+ " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
+ " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
+ " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
+ " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
+ " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
+ " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
+ " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
+ " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
+ " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
+ " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
+ " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
+ "// line.sec1 { }\n"
+ " line.sec5 { stroke-width: 2; }\n"
+ " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
+ " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
+ " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
+ " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
+ " text.sec { font-size: 10px; }\n"
+ " ]]>\n </style>\n</defs>\n\n");
+
+ svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
+ svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
+ svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
+ isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name,
+ strempty(host->hostname),
+ strempty(host->kernel_name),
+ strempty(host->kernel_release),
+ strempty(host->kernel_version),
+ strempty(host->architecture),
+ strempty(host->virtualization));
+
+ svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
+ svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
+
+ if (boot->firmware_time) {
+ svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
+ svg_text(true, -(double) boot->firmware_time, y, "firmware");
+ y++;
+ }
+ if (boot->loader_time) {
+ svg_bar("loader", -(double) boot->loader_time, 0, y);
+ svg_text(true, -(double) boot->loader_time, y, "loader");
+ y++;
+ }
+ if (boot->kernel_time) {
+ svg_bar("kernel", 0, boot->kernel_done_time, y);
+ svg_text(true, 0, y, "kernel");
+ y++;
+ }
+ if (boot->initrd_time) {
+ svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
+ svg_text(true, boot->initrd_time, y, "initrd");
+ y++;
+ }
+ svg_bar("active", boot->userspace_time, boot->finish_time, y);
+ 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++;
+ }
+
+ svg("</g>\n");
+
+ /* Legend */
+ svg("<g transform=\"translate(20,100)\">\n");
+ y++;
+ svg_bar("activating", 0, 300000, y);
+ svg_text(true, 400000, y, "Activating");
+ y++;
+ svg_bar("active", 0, 300000, y);
+ svg_text(true, 400000, y, "Active");
+ y++;
+ 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++;
+ svg_bar("generators", 0, 300000, y);
+ svg_text(true, 400000, y, "Generators");
+ y++;
+ svg_bar("unitsload", 0, 300000, y);
+ svg_text(true, 400000, y, "Loading unit files");
+ y++;
+
+ svg("</g>\n\n");
+
+ svg("</svg>\n");
+
+ free_unit_times(times, (unsigned) n);
+
+ n = 0;
+ return n;
+}
+
+static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
+ bool last, struct unit_times *times, struct boot_times *boot) {
+ unsigned int i;
+ char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
+
+ for (i = level; i != 0; i--)
+ printf("%s", special_glyph(branches & (1 << (i-1)) ? TREE_VERTICAL : TREE_SPACE));
+
+ printf("%s", special_glyph(last ? TREE_RIGHT : TREE_BRANCH));
+
+ if (times) {
+ if (times->time)
+ printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
+ format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
+ format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ansi_normal());
+ else if (times->activated > boot->userspace_time)
+ printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
+ else
+ printf("%s", name);
+ } else
+ printf("%s", name);
+ printf("\n");
+
+ return 0;
+}
+
+static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
+ _cleanup_free_ char *path = NULL;
+
+ assert(bus);
+ assert(name);
+ assert(deps);
+
+ path = unit_dbus_path_from_name(name);
+ if (path == NULL)
+ return -ENOMEM;
+
+ return bus_get_unit_property_strv(bus, path, "After", deps);
+}
+
+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;
+ usec_t usa = 0, usb = 0;
+ struct unit_times *times;
+
+ times = hashmap_get(unit_times_hashmap, *a);
+ if (times)
+ usa = times->activated;
+ times = hashmap_get(unit_times_hashmap, *b);
+ if (times)
+ usb = times->activated;
+
+ return usb - usa;
+}
+
+static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
+ unsigned int branches) {
+ _cleanup_strv_free_ char **deps = NULL;
+ char **c;
+ int r = 0;
+ usec_t service_longest = 0;
+ int to_print = 0;
+ struct unit_times *times;
+ struct boot_times *boot;
+
+ if (strv_extend(units, name))
+ return log_oom();
+
+ r = list_dependencies_get_dependencies(bus, name, &deps);
+ if (r < 0)
+ return r;
+
+ qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
+
+ r = acquire_boot_times(bus, &boot);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(c, deps) {
+ times = hashmap_get(unit_times_hashmap, *c);
+ if (times
+ && times->activated
+ && times->activated <= boot->finish_time
+ && (times->activated >= service_longest
+ || service_longest == 0)) {
+ service_longest = times->activated;
+ break;
+ }
+ }
+
+ if (service_longest == 0 )
+ return r;
+
+ STRV_FOREACH(c, deps) {
+ times = hashmap_get(unit_times_hashmap, *c);
+ if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz)
+ to_print++;
+ }
+
+ if (!to_print)
+ return r;
+
+ STRV_FOREACH(c, deps) {
+ times = hashmap_get(unit_times_hashmap, *c);
+ if (!times
+ || !times->activated
+ || times->activated > boot->finish_time
+ || service_longest - times->activated > arg_fuzz)
+ continue;
+
+ to_print--;
+
+ r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
+ if (r < 0)
+ return r;
+
+ if (strv_contains(*units, *c)) {
+ r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
+ true, NULL, boot);
+ if (r < 0)
+ return r;
+ continue;
+ }
+
+ r = list_dependencies_one(bus, *c, level + 1, units,
+ (branches << 1) | (to_print ? 1 : 0));
+ if (r < 0)
+ return r;
+
+ if (!to_print)
+ break;
+ }
+ return 0;
+}
+
+static int list_dependencies(sd_bus *bus, const char *name) {
+ _cleanup_strv_free_ char **units = NULL;
+ char ts[FORMAT_TIMESPAN_MAX];
+ struct unit_times *times;
+ int r;
+ const char *id;
+ _cleanup_free_ char *path = NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ struct boot_times *boot;
+
+ assert(bus);
+
+ path = unit_dbus_path_from_name(name);
+ if (path == NULL)
+ return -ENOMEM;
+
+ r = sd_bus_get_property(
+ bus,
+ "org.freedesktop.systemd1",
+ path,
+ "org.freedesktop.systemd1.Unit",
+ "Id",
+ &error,
+ &reply,
+ "s");
+ if (r < 0) {
+ log_error("Failed to get ID: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ r = sd_bus_message_read(reply, "s", &id);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ times = hashmap_get(unit_times_hashmap, id);
+
+ r = acquire_boot_times(bus, &boot);
+ if (r < 0)
+ return r;
+
+ if (times) {
+ if (times->time)
+ printf("%s%s +%s%s\n", ansi_highlight_red(), id,
+ format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ansi_normal());
+ else if (times->activated > boot->userspace_time)
+ printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
+ else
+ printf("%s\n", id);
+ }
+
+ return list_dependencies_one(bus, name, 0, &units, 0);
+}
+
+static int analyze_critical_chain(sd_bus *bus, char *names[]) {
+ struct unit_times *times;
+ unsigned int i;
+ Hashmap *h;
+ int n, r;
+
+ n = acquire_time_data(bus, &times);
+ if (n <= 0)
+ return n;
+
+ h = hashmap_new(&string_hash_ops);
+ if (!h)
+ return -ENOMEM;
+
+ for (i = 0; i < (unsigned)n; i++) {
+ r = hashmap_put(h, times[i].name, &times[i]);
+ if (r < 0)
+ return r;
+ }
+ unit_times_hashmap = h;
+
+ pager_open(arg_no_pager, false);
+
+ 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");
+
+ if (!strv_isempty(names)) {
+ char **name;
+ STRV_FOREACH(name, names)
+ list_dependencies(bus, *name);
+ } else
+ list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
+
+ hashmap_free(h);
+ free_unit_times(times, (unsigned) n);
+ return 0;
+}
+
+static int analyze_blame(sd_bus *bus) {
+ struct unit_times *times;
+ unsigned i;
+ int n;
+
+ n = acquire_time_data(bus, &times);
+ if (n <= 0)
+ return n;
+
+ qsort(times, n, sizeof(struct unit_times), compare_unit_time);
+
+ pager_open(arg_no_pager, false);
+
+ for (i = 0; i < (unsigned) n; i++) {
+ 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);
+ }
+
+ free_unit_times(times, (unsigned) n);
+ return 0;
+}
+
+static int analyze_time(sd_bus *bus) {
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ r = pretty_boot_time(bus, &buf);
+ if (r < 0)
+ return r;
+
+ puts(buf);
+ return 0;
+}
+
+static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) {
+ _cleanup_strv_free_ char **units = NULL;
+ char **unit;
+ int r;
+ bool match_patterns;
+
+ assert(u);
+ assert(prop);
+ assert(color);
+
+ match_patterns = strv_fnmatch(patterns, u->id, 0);
+
+ if (!strv_isempty(from_patterns) &&
+ !match_patterns &&
+ !strv_fnmatch(from_patterns, u->id, 0))
+ return 0;
+
+ r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
+ if (r < 0)
+ return r;
+
+ STRV_FOREACH(unit, units) {
+ bool match_patterns2;
+
+ match_patterns2 = strv_fnmatch(patterns, *unit, 0);
+
+ if (!strv_isempty(to_patterns) &&
+ !match_patterns2 &&
+ !strv_fnmatch(to_patterns, *unit, 0))
+ continue;
+
+ if (!strv_isempty(patterns) && !match_patterns && !match_patterns2)
+ continue;
+
+ printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
+ }
+
+ return 0;
+}
+
+static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) {
+ int r;
+
+ assert(bus);
+ assert(u);
+
+ if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) {
+ r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+ }
+
+ if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) {
+ r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+ r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+ r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+ r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+}
+
+static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) {
+ _cleanup_strv_free_ char **expanded_patterns = NULL;
+ char **pattern;
+ int r;
+
+ STRV_FOREACH(pattern, patterns) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *unit = NULL, *unit_id = NULL;
+
+ if (strv_extend(&expanded_patterns, *pattern) < 0)
+ return log_oom();
+
+ if (string_is_glob(*pattern))
+ continue;
+
+ unit = unit_dbus_path_from_name(*pattern);
+ if (!unit)
+ return log_oom();
+
+ r = sd_bus_get_property_string(
+ bus,
+ "org.freedesktop.systemd1",
+ unit,
+ "org.freedesktop.systemd1.Unit",
+ "Id",
+ &error,
+ &unit_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
+
+ if (!streq(*pattern, unit_id)) {
+ if (strv_extend(&expanded_patterns, unit_id) < 0)
+ return log_oom();
+ }
+ }
+
+ *ret = expanded_patterns;
+ expanded_patterns = NULL; /* do not free */
+
+ return 0;
+}
+
+static int dot(sd_bus *bus, char* patterns[]) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_strv_free_ char **expanded_patterns = NULL;
+ _cleanup_strv_free_ char **expanded_from_patterns = NULL;
+ _cleanup_strv_free_ char **expanded_to_patterns = NULL;
+ int r;
+ UnitInfo u;
+
+ r = expand_patterns(bus, patterns, &expanded_patterns);
+ if (r < 0)
+ return r;
+
+ r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns);
+ if (r < 0)
+ return r;
+
+ r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "ListUnits",
+ &error,
+ &reply,
+ "");
+ if (r < 0) {
+ log_error("Failed to list units: %s", bus_error_message(&error, -r));
+ return r;
+ }
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ printf("digraph systemd {\n");
+
+ while ((r = bus_parse_unit_info(reply, &u)) > 0) {
+
+ r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns);
+ if (r < 0)
+ return r;
+ }
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ printf("}\n");
+
+ log_info(" Color legend: black = Requires\n"
+ " dark blue = Requisite\n"
+ " dark grey = Wants\n"
+ " red = Conflicts\n"
+ " green = After\n");
+
+ if (on_tty())
+ log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
+ "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
+
+ return 0;
+}
+
+static int dump(sd_bus *bus, char **args) {
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ const char *text = NULL;
+ int r;
+
+ if (!strv_isempty(args)) {
+ log_error("Too many arguments.");
+ return -E2BIG;
+ }
+
+ pager_open(arg_no_pager, false);
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "Dump",
+ &error,
+ &reply,
+ "");
+ if (r < 0)
+ return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_read(reply, "s", &text);
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ fputs(text, stdout);
+ return 0;
+}
+
+static int set_log_level(sd_bus *bus, char **args) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(args);
+
+ if (strv_length(args) != 1) {
+ log_error("This command expects one argument only.");
+ return -E2BIG;
+ }
+
+ r = sd_bus_set_property(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LogLevel",
+ &error,
+ "s",
+ args[0]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static int set_log_target(sd_bus *bus, char **args) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ int r;
+
+ assert(bus);
+ assert(args);
+
+ if (strv_length(args) != 1) {
+ log_error("This command expects one argument only.");
+ return -E2BIG;
+ }
+
+ r = sd_bus_set_property(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "LogTarget",
+ &error,
+ "s",
+ args[0]);
+ if (r < 0)
+ return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
+
+ return 0;
+}
+
+static void help(void) {
+
+ pager_open(arg_no_pager, false);
+
+ printf("%s [OPTIONS...] {COMMAND} ...\n\n"
+ "Profile systemd, show unit dependencies, check unit files.\n\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --no-pager Do not pipe output into a pager\n"
+ " --system Operate on system systemd instance\n"
+ " --user Operate on user systemd instance\n"
+ " -H --host=[USER@]HOST Operate on remote host\n"
+ " -M --machine=CONTAINER Operate on local container\n"
+ " --order Show only order in the graph\n"
+ " --require Show only requirement in the graph\n"
+ " --from-pattern=GLOB Show only origins in the graph\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"
+ "Commands:\n"
+ " time Print time spent in the kernel\n"
+ " blame Print list of running units ordered by time to init\n"
+ " critical-chain Print a tree of the time critical chain of units\n"
+ " plot Output SVG graphic showing service initialization\n"
+ " dot Output dependency graph in dot(1) format\n"
+ " set-log-level LEVEL Set logging threshold for manager\n"
+ " set-log-target TARGET Set logging target for manager\n"
+ " dump Output state serialization of service manager\n"
+ " verify FILE... Check unit files for correctness\n"
+ , program_invocation_short_name);
+
+ /* When updating this list, including descriptions, apply
+ * changes to shell-completion/bash/systemd-analyze and
+ * shell-completion/zsh/_systemd-analyze too. */
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_ORDER,
+ ARG_REQUIRE,
+ ARG_USER,
+ ARG_SYSTEM,
+ ARG_DOT_FROM_PATTERN,
+ ARG_DOT_TO_PATTERN,
+ ARG_FUZZ,
+ ARG_NO_PAGER,
+ ARG_MAN,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "order", no_argument, NULL, ARG_ORDER },
+ { "require", no_argument, NULL, ARG_REQUIRE },
+ { "user", no_argument, NULL, ARG_USER },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
+ { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
+ { "fuzz", required_argument, NULL, ARG_FUZZ },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "man", optional_argument, NULL, ARG_MAN },
+ { "host", required_argument, NULL, 'H' },
+ { "machine", required_argument, NULL, 'M' },
+ {}
+ };
+
+ int r, c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ help();
+ return 0;
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_USER:
+ arg_user = true;
+ break;
+
+ case ARG_SYSTEM:
+ arg_user = false;
+ break;
+
+ case ARG_ORDER:
+ arg_dot = DEP_ORDER;
+ break;
+
+ case ARG_REQUIRE:
+ arg_dot = DEP_REQUIRE;
+ break;
+
+ case ARG_DOT_FROM_PATTERN:
+ if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_DOT_TO_PATTERN:
+ if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
+ return log_oom();
+
+ break;
+
+ case ARG_FUZZ:
+ r = parse_sec(optarg, &arg_fuzz);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_NO_PAGER:
+ arg_no_pager = true;
+ break;
+
+ case 'H':
+ arg_transport = BUS_TRANSPORT_REMOTE;
+ arg_host = optarg;
+ break;
+
+ case 'M':
+ arg_transport = BUS_TRANSPORT_MACHINE;
+ arg_host = optarg;
+ break;
+
+ case ARG_MAN:
+ if (optarg) {
+ r = parse_boolean(optarg);
+ if (r < 0) {
+ log_error("Failed to parse --man= argument.");
+ return -EINVAL;
+ }
+
+ arg_man = !!r;
+ } else
+ arg_man = true;
+
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached("Unhandled option code.");
+ }
+
+ return 1; /* work to do */
+}
+
+int main(int argc, char *argv[]) {
+ int r;
+
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
+ log_parse_environment();
+ log_open();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ goto finish;
+
+ if (streq_ptr(argv[optind], "verify"))
+ r = verify_units(argv+optind+1,
+ arg_user ? UNIT_FILE_USER : UNIT_FILE_SYSTEM,
+ arg_man);
+ else {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+
+ r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create bus connection: %m");
+ goto finish;
+ }
+
+ if (!argv[optind] || streq(argv[optind], "time"))
+ r = analyze_time(bus);
+ else if (streq(argv[optind], "blame"))
+ r = analyze_blame(bus);
+ else if (streq(argv[optind], "critical-chain"))
+ r = analyze_critical_chain(bus, argv+optind+1);
+ else if (streq(argv[optind], "plot"))
+ r = analyze_plot(bus);
+ else if (streq(argv[optind], "dot"))
+ r = dot(bus, argv+optind+1);
+ else if (streq(argv[optind], "dump"))
+ r = dump(bus, argv+optind+1);
+ else if (streq(argv[optind], "set-log-level"))
+ r = set_log_level(bus, argv+optind+1);
+ else if (streq(argv[optind], "set-log-target"))
+ r = set_log_target(bus, argv+optind+1);
+ else
+ log_error("Unknown operation '%s'.", argv[optind]);
+ }
+
+finish:
+ pager_close();
+
+ strv_free(arg_dot_from_patterns);
+ strv_free(arg_dot_to_patterns);
+
+ return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash
new file mode 100644
index 0000000000..7a5f46ba1d
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.bash
@@ -0,0 +1,117 @@
+# systemd-analyze(1) completion -*- shell-script -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010 Ran Benita
+# Copyright 2013 Harald Hoyer
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+
+__contains_word () {
+ local w word=$1; shift
+ for w in "$@"; do
+ [[ $w = "$word" ]] && return
+ done
+}
+
+__get_machines() {
+ local a b
+ machinectl list --no-legend --no-pager | { while read a b; do echo " $a"; done; };
+}
+
+_systemd_analyze() {
+ local i verb comps
+ local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
+
+ local -A OPTS=(
+ [STANDALONE]='--help --version --system --user --order --require --no-pager --man'
+ [ARG]='-H --host -M --machine --fuzz --from-pattern --to-pattern '
+ )
+
+ local -A VERBS=(
+ [STANDALONE]='time blame plot dump'
+ [CRITICAL_CHAIN]='critical-chain'
+ [DOT]='dot'
+ [LOG_LEVEL]='set-log-level'
+ [VERIFY]='verify'
+ )
+
+ _init_completion || return
+
+ for ((i=0; i < COMP_CWORD; i++)); do
+ if __contains_word "${COMP_WORDS[i]}" ${VERBS[*]} &&
+ ! __contains_word "${COMP_WORDS[i-1]}" ${OPTS[ARG]}; then
+ verb=${COMP_WORDS[i]}
+ break
+ fi
+ done
+
+ if __contains_word "$prev" ${OPTS[ARG]}; then
+ case $prev in
+ --host|-H)
+ comps=$(compgen -A hostname)
+ ;;
+ --machine|-M)
+ comps=$( __get_machines )
+ ;;
+ esac
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+ fi
+
+ if [[ -z $verb && $cur = -* ]]; then
+ COMPREPLY=( $(compgen -W '${OPTS[*]}' -- "$cur") )
+ return 0
+ fi
+
+ if [[ -z $verb ]]; then
+ comps=${VERBS[*]}
+
+ elif __contains_word "$verb" ${VERBS[STANDALONE]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user'
+ fi
+
+ elif __contains_word "$verb" ${VERBS[CRITICAL_CHAIN]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user --fuzz'
+ fi
+
+ elif __contains_word "$verb" ${VERBS[DOT]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user --from-pattern --to-pattern --order --require'
+ fi
+
+ elif __contains_word "$verb" ${VERBS[LOG_LEVEL]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user'
+ else
+ comps='debug info notice warning err crit alert emerg'
+ fi
+
+ elif __contains_word "$verb" ${VERBS[VERIFY]}; then
+ if [[ $cur = -* ]]; then
+ comps='--help --version --system --user --man'
+ else
+ comps=$( compgen -A file -- "$cur" )
+ compopt -o filenames
+ fi
+
+ fi
+
+ COMPREPLY=( $(compgen -W '$comps' -- "$cur") )
+ return 0
+}
+
+complete -F _systemd_analyze systemd-analyze
diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh
new file mode 100644
index 0000000000..efafddc686
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.completion.zsh
@@ -0,0 +1,58 @@
+#compdef systemd-analyze
+
+_systemd_analyze_set-log-level() {
+ local -a _levels
+ _levels=(debug info notice warning err crit alert emerg)
+ _describe -t level 'logging level' _levels || compadd "$@"
+}
+
+_systemd_analyze_verify() {
+ _sd_unit_files
+}
+
+_systemd_analyze_command(){
+ local -a _systemd_analyze_cmds
+ # Descriptions taken from systemd-analyze --help.
+ _systemd_analyze_cmds=(
+ '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'
+ 'dot:Dump dependency graph (in dot(1) format)'
+ 'dump:Dump server status'
+ 'set-log-level:Set systemd log threshold'
+ 'verify:Check unit files for correctness'
+ )
+
+ if (( CURRENT == 1 )); then
+ _describe "options" _systemd_analyze_cmds
+ else
+ local curcontext="$curcontext"
+ cmd="${${_systemd_analyze_cmds[(r)$words[1]:*]%%:*}}"
+ if (( $#cmd )); then
+ if (( $+functions[_systemd_analyze_$cmd] )) && (( CURRENT == 2 )); then
+ _systemd_analyze_$cmd
+ else
+ _message "no more options"
+ fi
+ else
+ _message "unknown systemd-analyze command: $words[1]"
+ fi
+ fi
+}
+
+_arguments \
+ {-h,--help}'[Show help text]' \
+ '--version[Show package version]' \
+ '--system[Operate on system systemd instance]' \
+ '--user[Operate on user systemd instance]' \
+ '--no-pager[Do not pipe output into a pager]' \
+ '--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \
+ '--order[When generating graph for dot, show only order]' \
+ '--require[When generating graph for dot, show only requirement]' \
+ '--fuzz=[When printing the tree of the critical chain, print also services, which finished TIMESPAN earlier, than the latest in the branch]:TIMESPAN' \
+ '--from-pattern=[When generating a dependency graph, filter only origins]:GLOB' \
+ '--to-pattern=[When generating a dependency graph, filter only destinations]:GLOB' \
+ {-H+,--host=}'[Operate on remote host]:userathost:_sd_hosts_or_user_at_host' \
+ {-M+,--machine=}'[Operate on local container]:machine:_sd_machines' \
+ '*::systemd-analyze commands:_systemd_analyze_command'
diff --git a/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml
new file mode 100644
index 0000000000..8fa7cd3329
--- /dev/null
+++ b/src/grp-system/grp-utils/systemd-analyze/systemd-analyze.xml
@@ -0,0 +1,389 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2012 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-analyze"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-analyze</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Lennart</firstname>
+ <surname>Poettering</surname>
+ <email>lennart@poettering.net</email>
+ </author>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Harald</firstname>
+ <surname>Hoyer</surname>
+ <email>harald@redhat.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-analyze</refentrytitle>
+ <manvolnum>1</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-analyze</refname>
+ <refpurpose>Analyze system boot-up performance</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg>time</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">blame</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">critical-chain</arg>
+ <arg choice="opt" rep="repeat"><replaceable>UNIT</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">plot</arg>
+ <arg choice="opt">&gt; file.svg</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">dot</arg>
+ <arg choice="opt" rep="repeat"><replaceable>PATTERN</replaceable></arg>
+ <arg choice="opt">&gt; file.dot</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">dump</arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">set-log-level</arg>
+ <arg choice="plain"><replaceable>LEVEL</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">set-log-target</arg>
+ <arg choice="plain"><replaceable>TARGET</replaceable></arg>
+ </cmdsynopsis>
+ <cmdsynopsis>
+ <command>systemd-analyze</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="plain">verify</arg>
+ <arg choice="opt" rep="repeat"><replaceable>FILES</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para><command>systemd-analyze</command> may be used to determine
+ system boot-up performance statistics and retrieve other state and
+ tracing information from the system and service manager, and to
+ verify the correctness of unit files.</para>
+
+ <para><command>systemd-analyze time</command> prints the time
+ spent in the kernel before userspace has been reached, the time
+ spent in the initial RAM disk (initrd) before normal system
+ userspace has been reached, and the time normal system userspace
+ took to initialize. Note that these measurements simply measure
+ the time passed up to the point where all system services have
+ been spawned, but not necessarily until they fully finished
+ initialization or the disk is idle.</para>
+
+ <para><command>systemd-analyze blame</command> prints a list of
+ all running units, ordered by the time they took to initialize.
+ This information may be used to optimize boot-up times. Note that
+ the output might be misleading as the initialization of one
+ service might be slow simply because it waits for the
+ initialization of another service to complete.</para>
+
+ <para><command>systemd-analyze critical-chain
+ [<replaceable>UNIT...</replaceable>]</command> prints a tree of
+ the time-critical chain of units (for each of the specified
+ <replaceable>UNIT</replaceable>s or for the default target
+ otherwise). The time after the unit is active or started is
+ printed after the "@" character. The time the unit takes to start
+ is printed after the "+" character. Note that the output might be
+ misleading as the initialization of one service might depend on
+ socket activation and because of the parallel execution of
+ units.</para>
+
+ <para><command>systemd-analyze plot</command> prints an SVG
+ graphic detailing which system services have been started at what
+ time, highlighting the time they spent on initialization.</para>
+
+ <para><command>systemd-analyze dot</command> generates textual
+ dependency graph description in dot format for further processing
+ with the GraphViz
+ <citerefentry project='die-net'><refentrytitle>dot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ tool. Use a command line like <command>systemd-analyze dot | dot
+ -Tsvg > systemd.svg</command> to generate a graphical dependency
+ tree. Unless <option>--order</option> or
+ <option>--require</option> is passed, the generated graph will
+ show both ordering and requirement dependencies. Optional pattern
+ globbing style specifications (e.g. <filename>*.target</filename>)
+ may be given at the end. A unit dependency is included in the
+ graph if any of these patterns match either the origin or
+ destination node.</para>
+
+ <para><command>systemd-analyze dump</command> outputs a (usually
+ very long) human-readable serialization of the complete server
+ state. Its format is subject to change without notice and should
+ not be parsed by applications.</para>
+
+ <para><command>systemd-analyze set-log-level
+ <replaceable>LEVEL</replaceable></command> changes the current log
+ level of the <command>systemd</command> daemon to
+ <replaceable>LEVEL</replaceable> (accepts the same values as
+ <option>--log-level=</option> described in
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para>
+
+ <para><command>systemd-analyze set-log-target
+ <replaceable>TARGET</replaceable></command> changes the current log
+ target of the <command>systemd</command> daemon to
+ <replaceable>TARGET</replaceable> (accepts the same values as
+ <option>--log-target=</option>, described in
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para>
+
+ <para><command>systemd-analyze verify</command> will load unit files and print
+ warnings if any errors are detected. Files specified on the command line will be
+ loaded, but also any other units referenced by them. The full unit search path is
+ formed by combining the directories for all command line arguments, and the usual unit
+ load paths (variable <varname>$SYSTEMD_UNIT_PATH</varname> is supported, and may be
+ used to replace or augment the compiled in set of unit load paths; see
+ <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ All units files present in the directories containing the command line arguments will
+ be used in preference to the other paths.</para>
+
+ <para>If no command is passed, <command>systemd-analyze
+ time</command> is implied.</para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--user</option></term>
+
+ <listitem><para>Operates on the user systemd
+ instance.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--system</option></term>
+
+ <listitem><para>Operates on the system systemd instance. This
+ is the implied default.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--order</option></term>
+ <term><option>--require</option></term>
+
+ <listitem><para>When used in conjunction with the
+ <command>dot</command> command (see above), selects which
+ dependencies are shown in the dependency graph. If
+ <option>--order</option> is passed, only dependencies of type
+ <varname>After=</varname> or <varname>Before=</varname> are
+ shown. If <option>--require</option> is passed, only
+ dependencies of type <varname>Requires=</varname>,
+ <varname>Requisite=</varname>,
+ <varname>Wants=</varname> and <varname>Conflicts=</varname>
+ are shown. If neither is passed, this shows dependencies of
+ all these types.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--from-pattern=</option></term>
+ <term><option>--to-pattern=</option></term>
+
+ <listitem><para>When used in conjunction with the
+ <command>dot</command> command (see above), this selects which
+ relationships are shown in the dependency graph. Both options
+ require a
+ <citerefentry project='die-net'><refentrytitle>glob</refentrytitle><manvolnum>7</manvolnum></citerefentry>
+ pattern as an argument, which will be matched against the
+ left-hand and the right-hand, respectively, nodes of a
+ relationship.</para>
+
+ <para>Each of these can be used more than once, in which case
+ the unit name must match one of the values. When tests for
+ both sides of the relation are present, a relation must pass
+ both tests to be shown. When patterns are also specified as
+ positional arguments, they must match at least one side of the
+ relation. In other words, patterns specified with those two
+ options will trim the list of edges matched by the positional
+ arguments, if any are given, and fully determine the list of
+ edges shown otherwise.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--fuzz=</option><replaceable>timespan</replaceable></term>
+
+ <listitem><para>When used in conjunction with the
+ <command>critical-chain</command> command (see above), also
+ show units, which finished <replaceable>timespan</replaceable>
+ earlier, than the latest unit in the same level. The unit of
+ <replaceable>timespan</replaceable> is seconds unless
+ specified with a different unit, e.g.
+ "50ms".</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--no-man</option></term>
+
+ <listitem><para>Do not invoke man to verify the existence of
+ man pages listed in <varname>Documentation=</varname>.
+ </para></listitem>
+ </varlistentry>
+
+ <xi:include href="user-system-options.xml" xpointer="host" />
+ <xi:include href="user-system-options.xml" xpointer="machine" />
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ <xi:include href="standard-options.xml" xpointer="no-pager" />
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Exit status</title>
+
+ <para>On success, 0 is returned, a non-zero failure code
+ otherwise.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples for <command>dot</command></title>
+
+ <example>
+ <title>Plots all dependencies of any unit whose name starts with
+ <literal>avahi-daemon</literal></title>
+
+ <programlisting>$ systemd-analyze dot 'avahi-daemon.*' | dot -Tsvg > avahi.svg
+ $ eog avahi.svg</programlisting>
+ </example>
+
+ <example>
+ <title>Plots the dependencies between all known target units</title>
+
+ <programlisting>systemd-analyze dot --to-pattern='*.target' --from-pattern='*.target' | dot -Tsvg > targets.svg
+$ eog targets.svg</programlisting>
+ </example>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples for <command>verify</command></title>
+
+ <para>The following errors are currently detected:</para>
+ <itemizedlist>
+ <listitem><para>unknown sections and directives,
+ </para></listitem>
+
+ <listitem><para>missing dependencies which are required to start
+ the given unit, </para></listitem>
+
+ <listitem><para>man pages listed in
+ <varname>Documentation=</varname> which are not found in the
+ system,</para></listitem>
+
+ <listitem><para>commands listed in <varname>ExecStart=</varname>
+ and similar which are not found in the system or not
+ executable.</para></listitem>
+ </itemizedlist>
+
+ <example>
+ <title>Misspelt directives</title>
+
+ <programlisting>$ cat ./user.slice
+[Unit]
+WhatIsThis=11
+Documentation=man:nosuchfile(1)
+Requires=different.service
+
+[Service]
+Desription=x
+
+$ systemd-analyze verify ./user.slice
+[./user.slice:9] Unknown lvalue 'WhatIsThis' in section 'Unit'
+[./user.slice:13] Unknown section 'Service'. Ignoring.
+Error: org.freedesktop.systemd1.LoadFailed:
+ Unit different.service failed to load:
+ No such file or directory.
+Failed to create user.slice/start: Invalid argument
+user.slice: man nosuchfile(1) command failed with code 16
+ </programlisting>
+ </example>
+
+ <example>
+ <title>Missing service units</title>
+
+ <programlisting>$ tail ./a.socket ./b.socket
+==> ./a.socket &lt;==
+[Socket]
+ListenStream=100
+
+==> ./b.socket &lt;==
+[Socket]
+ListenStream=100
+Accept=yes
+
+$ systemd-analyze verify ./a.socket ./b.socket
+Service a.service not loaded, a.socket cannot be started.
+Service b@0.service not loaded, b.socket cannot be started.
+ </programlisting>
+ </example>
+ </refsect1>
+
+ <xi:include href="less-variables.xml" />
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>