From e737d1ac24aa3f07bde0a03ad686927a5f612ec2 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Perennou Date: Thu, 31 Jan 2013 10:12:05 +0100 Subject: [PATCH 4/5] systemd-analyze: rewrite in C Signed-off-by: Marc-Antoine Perennou --- .gitignore | 1 + Makefile.am | 21 +- configure.ac | 8 + src/analyze/.gitignore | 1 - src/analyze/systemd-analyze.c | 682 +++++++++++++++++++++++++++++++++++++++++ src/analyze/systemd-analyze.in | 328 -------------------- 6 files changed, 706 insertions(+), 335 deletions(-) delete mode 100644 src/analyze/.gitignore create mode 100644 src/analyze/systemd-analyze.c delete mode 100755 src/analyze/systemd-analyze.in diff --git a/.gitignore b/.gitignore index 03ed554..c287859 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /systemctl /systemd /systemd-ac-power +/systemd-analyze /systemd-ask-password /systemd-binfmt /systemd-bootchart diff --git a/Makefile.am b/Makefile.am index 88662c0..517a588 100644 --- a/Makefile.am +++ b/Makefile.am @@ -299,14 +299,23 @@ systemgenerator_PROGRAMS = \ systemd-system-update-generator \ systemd-efi-boot-generator -dist_bin_SCRIPTS = \ - src/analyze/systemd-analyze +if ENABLE_ANALYZE +bin_PROGRAMS += \ + systemd-analyze -EXTRA_DIST += \ - src/analyze/systemd-analyze.in +systemd_analyze_SOURCES = \ + src/analyze/systemd-analyze.c -CLEANFILES += \ - src/analyze/systemd-analyze +systemd_analyze_CFLAGS = \ + $(AM_CFLAGS) \ + $(CAIRO_CFLAGS) \ + $(DBUS_CFLAGS) + +systemd_analyze_LDADD = \ + $(CAIRO_LIBS) \ + libsystemd-dbus.la \ + libsystemd-logs.la +endif dist_bashcompletion_DATA = \ shell-completion/systemd-bash-completion.sh diff --git a/configure.ac b/configure.ac index d94af7b..ccc1037 100644 --- a/configure.ac +++ b/configure.ac @@ -558,6 +558,13 @@ fi AM_CONDITIONAL(ENABLE_READAHEAD, [test "$have_readahead" = "yes"]) # ------------------------------------------------------------------------------ +AC_ARG_ENABLE([analyze], + AS_HELP_STRING([--disable-analyze], [disable systemd-analyze support @<:@default=enabled@:>@]), + [], [enable_analyze=yes]) +AS_IF([test "x$enable_analyze" = "xyes"], [ PKG_CHECK_MODULES([CAIRO], [cairo cairo-svg]) ]) +AM_CONDITIONAL([ENABLE_ANALYZE], [test "x$enable_analyze" = "xyes"]) + +# ------------------------------------------------------------------------------ have_bootchart=no AC_ARG_ENABLE(bootchart, AS_HELP_STRING([--disable-bootchart], [disable bootchart tool])) if test "x$enable_bootchart" != "xno"; then @@ -850,6 +857,7 @@ AC_MSG_RESULT([ QRENCODE: ${have_qrencode} MICROHTTPD: ${have_microhttpd} CHKCONFIG: ${have_chkconfig} + abalyze: ${enable_analyze} binfmt: ${have_binfmt} vconsole: ${have_vconsole} readahead: ${have_readahead} diff --git a/src/analyze/.gitignore b/src/analyze/.gitignore deleted file mode 100644 index 752ea23..0000000 --- a/src/analyze/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/systemd-analyze diff --git a/src/analyze/systemd-analyze.c b/src/analyze/systemd-analyze.c new file mode 100644 index 0000000..6e522d9 --- /dev/null +++ b/src/analyze/systemd-analyze.c @@ -0,0 +1,682 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010-2013 Lennart Poettering + Copyright 2013 Marc-Antoine Perennou + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "log.h" +#include "util.h" +#include "macro.h" +#include "dbus-common.h" + +static bool arg_user = false; + +static int bus_get_uint64_property (DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) { + + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + int r; + DBusMessageIter iter, sub; + + r = bus_method_call_with_reply ( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "Get", + &reply, + NULL, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID); + if (r < 0) + return r; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_recurse(&iter, &sub); + + if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_get_basic(&sub, val); + + return 0; +} + +struct TimeData { + char *name; + uint64_t ixt; + uint64_t aet; + uint64_t axt; + uint64_t iet; +}; + +static int acquire_time_data(DBusConnection *bus, struct TimeData **l, unsigned *c) { + + _cleanup_dbus_message_unref_ DBusMessage *reply = NULL; + DBusMessageIter iter, sub; + unsigned n_units = 0; + int r; + + assert (*l == NULL); + + r = bus_method_call_with_reply( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &reply, + NULL, + DBUS_TYPE_INVALID); + if (r < 0) + return r; + + if (!dbus_message_iter_init(reply, &iter) || + dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) { + log_error("Failed to parse reply."); + return -EIO; + } + + dbus_message_iter_recurse(&iter, &sub); + + *c = 0; + while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) { + struct unit_info u; + struct TimeData *data; + + assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRUCT); + + if (*c >= n_units) { + struct TimeData *t; + + n_units = MAX(2*(*c), 16); + t = realloc(*l, sizeof(struct TimeData) * n_units); + if (!t) + return log_oom(); + + *l = t; + } + + data = *l + *c; + + r = bus_parse_unit_info(&sub, &u); + if (r < 0) + goto next_unit; + + if (!streq(u.following, "")) + goto next_unit; + + if (bus_get_uint64_property(bus, u.unit_path, "org.freedesktop.systemd1.Unit", "InactiveExitTimestampMonotonic", &data->ixt) < 0 || + bus_get_uint64_property(bus, u.unit_path, "org.freedesktop.systemd1.Unit", "ActiveEnterTimestampMonotonic", &data->aet) < 0 || + bus_get_uint64_property(bus, u.unit_path, "org.freedesktop.systemd1.Unit", "ActiveExitTimestampMonotonic", &data->axt) < 0 || + bus_get_uint64_property(bus, u.unit_path, "org.freedesktop.systemd1.Unit", "InactiveEnterTimestampMonotonic", &data->iet) < 0) + return -EIO; + + data->name = strdup(u.id); + ++(*c); +next_unit: + dbus_message_iter_next(&sub); + } + + return 0; +} + +struct StartTime { + uint64_t firmware_time; + uint64_t loader_time; + uint64_t kernel_time; + uint64_t initrd_time; + uint64_t userspace_time; + uint64_t finish_time; +}; + +static int acquire_start_time(DBusConnection *bus, struct StartTime *time) { + + /*** + Note that the firmware/loader times are returned as positive + values but are actually considered negative from the point + in time of kernel initialization. Also, the monotonic kernel + time will always be 0 since that's the epoch of the + monotonic clock. Since we want to know whether the kernel + timestamp is set at all we will instead ask for the realtime + clock for this timestamp. + ***/ + + if (bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "FirmwareTimestampMonotonic", &time->firmware_time) < 0 || + bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "LoaderTimestampMonotonic", &time->loader_time) < 0 || + bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "KernelTimestamp", &time->kernel_time) < 0 || + bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "InitRDTimestampMonotonic", &time->initrd_time) < 0 || + bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UserspaceTimestampMonotonic", &time->userspace_time) < 0 || + bus_get_uint64_property(bus, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "FinishTimestampMonotonic", &time->finish_time) < 0) + return -EIO; + + if (!time->finish_time) { + log_error("Bootup is not yet finished. Please try again later."); + return -1; + } + + assert(time->firmware_time >= time->loader_time); + assert(time->initrd_time <= time->userspace_time); + assert(time->userspace_time <= time->finish_time); + + return 0; +} + +static void draw_box(cairo_t *context, double j, double k, double l, double m, double r, double g, double b) { + + cairo_save (context); + cairo_set_source_rgb (context, r, g, b); + cairo_rectangle (context, j, k, l, m); + cairo_fill (context); + cairo_restore (context); +} + +static void draw_text(cairo_t *context, double x, double y, const char *text, double vcenter, double hcenter) { + + cairo_text_extents_t extents; + + cairo_save (context); + + cairo_set_source_rgb (context, 0, 0, 0); + cairo_select_font_face (context, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size (context, 12); + + cairo_text_extents (context, text, &extents); + x = x - extents.width*hcenter - extents.x_bearing; + y = y - extents.height*vcenter - extents.y_bearing; + + cairo_move_to (context, x, y); + cairo_show_text (context, text); + + cairo_restore (context); +} + +static int _time(DBusConnection *bus) { + + struct StartTime time; + int r; + + r = acquire_start_time(bus, &time); + if (r < 0) + return r; + + printf("Startup finished in "); + if (time.firmware_time > 0) + printf("%lums (firmware) + ", (time.firmware_time - time.loader_time) / 1000); + if (time.loader_time > 0) + printf("%lums (loader) + ", time.loader_time / 1000); + if (time.initrd_time > 0) + printf("%lums (kernel) + %lums (initrd) + ", time.initrd_time / 1000, (time.userspace_time - time.initrd_time) / 1000); + else if (time.kernel_time > 0) + printf("%lums (kernel) + ", time.userspace_time / 1000); + printf("%lums (userspace) ", (time.finish_time - time.userspace_time) / 1000); + if (time.kernel_time > 0) + printf("= %lums\n", (time.firmware_time + time.finish_time) / 1000); + else + printf("= %lums\n", (time.finish_time - time.userspace_time) / 1000); + + return 0; +} + +static int blame_time_data_sort(const void *aa, const void *bb) { + const struct TimeData *a = aa, *b = bb; + return (int) ((b->aet - b->ixt) - (a->aet - a->ixt)); +} + +static int blame(DBusConnection *bus) { + + _cleanup_free_ struct TimeData *data = NULL; + struct TimeData *d; + int r; + unsigned c; + + r = acquire_time_data(bus, &data, &c); + if (r < 0) + return r; + + qsort(data, c, sizeof(struct TimeData), blame_time_data_sort); + for (d = data; d < data + c; ++d) { + if (d->ixt <= 0 || d->aet <= 0) + continue; + if (d->aet <= d->ixt) + continue; + printf("%6lums %s\n", (d->aet - d->ixt) / 1000, d->name); + } + + return 0; +} + +static int plot_time_data_sort(const void *aa, const void *bb) { + + const struct TimeData *a = aa, *b = bb; + return (int) (a->ixt - b->ixt); +} + +static int find_min_above (int limit, int val, ...) { + + va_list v; + int min = -1; + + va_start (v, val); + while (val >= 0) { + if (val > limit && (min < 0 || val < min)) + min = val; + val = va_arg (v, int); + } + va_end (v); + + return (min >= limit) ? min : limit; +} + +static char *read_osrel(void) { + + _cleanup_fclose_ FILE *f; + _cleanup_free_ char *line = NULL; + size_t len; + ssize_t read; + char *osrel = NULL; + + f = fopen("/etc/os-release", "re"); + if (!f) + return NULL; + + while ((read = getline(&line, &len, f)) != -1) { + if (startswith (line, "PRETTY_NAME=")) { + if (line[12] == '"') { + line[read-2] = '\0'; + osrel = strdup(line+13); + } + else { + line[read-1] = '\0'; + osrel = strdup(line+12); + } + break; + } + } + + return osrel; +} + +static int plot (DBusConnection *bus) { + struct StartTime time; + _cleanup_free_ struct TimeData *data = NULL; + struct TimeData *d; + unsigned c; + int r, count, i = 0, y = 0; + double border = 100, bar_height = 20, bar_space = bar_height * 0.1, + width, height; + cairo_surface_t *surface; + cairo_t *context; + char *text = NULL; + struct utsname name; + char *osrel; + + r = uname(&name); + if (r < 0) { + log_error("Cannot get system name: %m"); + return -errno; + } + + r = acquire_start_time (bus, &time); + if (r < 0) + return r; + + r = acquire_time_data (bus, &data, &c); + if (r < 0) + return r; + + qsort(data, c, sizeof(struct TimeData), plot_time_data_sort); + + // Account for kernel and initramfs bars if they exist + if (time.initrd_time > 0) + count = 3; + else + count = 2; + + for (d = data; d < data + c; ++d) { + if ((d->ixt >= time.userspace_time && d->ixt <= time.finish_time) || + (d->aet >= time.userspace_time && d->aet <= time.finish_time) || + (d->axt >= time.userspace_time && d->axt <= time.finish_time)) + ++count; + } + + // 1000px = 10s, 1px = 10ms + width = time.finish_time/10000 + border*2; + height = count * (bar_height + bar_space) + border * 2; + + if (width < 1000) + width = 1000; + + surface = cairo_svg_surface_create("/dev/stdout", width, height); + context = cairo_create(surface); + + draw_box(context, 0, 0, width, height, 1, 1, 1); + + cairo_translate(context, border + 0.5, border + 0.5); + + cairo_save (context); + cairo_set_line_width(context, 1); + cairo_set_source_rgb(context, 0.7, 0.7, 0.7); + + for (i = 0; i < (int)(time.finish_time/10000) + 100; i += 100) { + cairo_move_to(context, i, 0); + cairo_line_to(context, i, height-border*2); + } + + cairo_move_to(context, 0, 0); + cairo_line_to(context, width-border*2, 0); + + cairo_move_to(context, 0, height-border*2); + cairo_line_to(context, width-border*2, height-border*2); + + cairo_stroke(context); + cairo_restore(context); + + osrel = read_osrel(); + + asprintf(&text, "%s %s (%s %s) %s", osrel ? osrel : "Linux", name.nodename, name.release, name.version, name.machine); + draw_text(context, 0, -15, text, 1, 0); + free (text); + text = NULL; + if (osrel) + free(osrel); + + for (i = 0; i < (int)(time.finish_time/10000) + 100; i += 100) { + r = asprintf(&text, "%lus", (unsigned long)(i/100)); + if (r < 1) + return log_oom(); + + draw_text(context, i, -13, text, 0, 0); + free (text); + text = NULL; + } + + // draw boxes for kernel and initramfs boot time + if (time.initrd_time > 0) { + draw_box(context, 0, y, time.initrd_time/10000, bar_height, 0.7, 0.7, 0.7); + draw_text(context, 10, y + bar_height/2, "kernel", 0.5, 0); + y += bar_height + bar_space; + + draw_box(context, time.initrd_time/10000, y, time.userspace_time/10000-time.initrd_time/10000, bar_height, 0.7, 0.7, 0.7); + draw_text(context, time.initrd_time/10000 + 10, y + bar_height/2, "initramfs", 0.5, 0); + y += bar_height + bar_space; + } + else { + draw_box(context, 0, y, time.userspace_time/10000, bar_height, 0.6, 0.6, 0.6); + draw_text(context, 10, y + bar_height/2, "kernel", 0.5, 0); + y += bar_height + bar_space; + } + + draw_box(context, time.userspace_time/10000, y, time.finish_time/10000-time.userspace_time/10000, bar_height, 0.7, 0.7, 0.7); + draw_text(context, time.userspace_time/10000 + 10, y + bar_height/2, "userspace", 0.5, 0); + y += bar_height + bar_space; + + for (d = data; d < data + c; ++d) { + bool drawn = false; + int left = -1; + uint64_t a, b = 0; + + if (d->ixt >= time.userspace_time && d->ixt <= time.finish_time) { + // Activating + a = d->ixt; + b = find_min_above(d->ixt, d->aet, d->axt, d->iet, time.finish_time, -1) - d->ixt; + + draw_box(context, a/10000, y, b/10000, bar_height, 1, 0, 0); + drawn = true; + + if (left < 0) + left = a; + } + + if (d->aet >= time.userspace_time && d->aet <= time.finish_time) { + // Active + a = d->aet; + b = find_min_above(d->aet, d->axt, d->iet, time.finish_time, -1) - d->aet; + + draw_box(context, a/10000, y, b/10000, bar_height, .8, .6, .6); + drawn = true; + + if (left < 0) + left = a; + } + + if (d->axt >= time.userspace_time && d->axt <= time.finish_time) { + // Deactivating + a = d->axt; + b = find_min_above(d->axt, d->iet, time.finish_time, -1) - d->axt; + + draw_box(context, a/10000, y, b/10000, bar_height, .6, .4, .4); + drawn = true; + + if (left < 0) + left = a; + } + + if (drawn) { + int x = left/10000; + + if (x < width/2-border) + draw_text(context, x + 10, y + bar_height/2, d->name, 0.5, 0); + else + draw_text(context, x - 10, y + bar_height/2, d->name, 0.5, 1); + + y += bar_height + bar_space; + } + + free(d->name); + } + + draw_text(context, 0, height-border*2, "Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating", -1, 0); + + if (time.initrd_time > 0) + r = asprintf(&text, "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums", + time.initrd_time/1000, (time.userspace_time - time.initrd_time)/1000, (time.finish_time - time.userspace_time)/1000, time.finish_time/1000); + else + r = asprintf(&text, "Startup finished in %lums (kernel) + %lums (userspace) = %lums", + time.userspace_time/1000, (time.finish_time - time.userspace_time)/1000, time.finish_time/1000); + if (r < 1) + return log_oom(); + + draw_text(context, 0, height-border*2 + bar_height, text, -1, 0); + free(text); + + cairo_surface_finish(surface); + + return 0; +} + +static int help(void) { + + printf("usage: %s [-h] [-v] [--user] [{time,blame,plot}]\n\n" + "Process systemd profiling information\n\n" + "positional arguments:\n" + " {time,blame,plot} action to perform (default: time)\n\n" + "optional arguments:\n" + " -h, --help show this help message and exit\n" + " -v, --version show program's version number and exit\n" + " --user use the session bus\n\n" + "time - print time spent in the kernel before reaching userspace\n" + "blame - print list of running units ordered by time to init\n" + "plot - output SVG graphic showing service initialization\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_USER + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { "user", no_argument, NULL, ARG_USER }, + { NULL, 0, NULL, 0 } + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hv", options, NULL)) >= 0) { + switch (c) { + case 'h': + help(); + return 0; + + case 'v': + puts(PACKAGE_STRING); + return 0; + + case ARG_USER: + arg_user = true; + break; + + } + } + + return 1; +} + +int main(int argc, char*argv[]) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(DBusConnection *bus); + } verbs[] = { + { "time", LESS, 1, _time }, + { "blame", LESS, 1, blame }, + { "plot", LESS, 1, plot }, + }; + + unsigned i; + int r, left, retval = EXIT_FAILURE; + + DBusConnection *bus = NULL; + DBusError error; + + dbus_error_init(&error); + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r < 0) + goto finish; + else if (r == 0) { + retval = EXIT_SUCCESS; + goto finish; + } + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "time" */ + i = 0; + else { + for (i = 0; i < ELEMENTSOF(verbs); ++i) + if (streq(argv[optind], verbs[i].verb)) + break; + } + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation '%s'.", argv[optind]); + return -EINVAL; + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, NULL, &error); + + r = verbs[i].dispatch(bus); + retval = r < 0 ? EXIT_FAILURE : r; + + if (bus) { + dbus_connection_flush(bus); + dbus_connection_close(bus); + dbus_connection_unref(bus); + } + +finish: + dbus_error_free(&error); + dbus_shutdown(); + + return retval; +} diff --git a/src/analyze/systemd-analyze.in b/src/analyze/systemd-analyze.in deleted file mode 100755 index e964bb3..0000000 --- a/src/analyze/systemd-analyze.in +++ /dev/null @@ -1,328 +0,0 @@ -#!@PYTHON_BINARY@ -# -*-python-*- - -# This file is part of systemd. -# -# Copyright 2010-2013 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 . - -import sys, os -import argparse -from gi.repository import Gio -try: - import cairo -except ImportError: - cairo = None - -def acquire_time_data(): - manager = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE, - None, 'org.freedesktop.systemd1', '/org/freedesktop/systemd1', 'org.freedesktop.systemd1.Manager', None) - units = manager.ListUnits() - - l = [] - - for i in units: - if i[5] != "": - continue - - properties = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE, - None, 'org.freedesktop.systemd1', i[6], 'org.freedesktop.DBus.Properties', None) - - ixt = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'InactiveExitTimestampMonotonic') - aet = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'ActiveEnterTimestampMonotonic') - axt = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'ActiveExitTimestampMonotonic') - iet = properties.Get('(ss)', 'org.freedesktop.systemd1.Unit', 'InactiveEnterTimestampMonotonic') - - l.append((str(i[0]), ixt, aet, axt, iet)) - - return l - -def acquire_start_time(): - properties = Gio.DBusProxy.new_for_bus_sync(bus, Gio.DBusProxyFlags.NONE, - None, 'org.freedesktop.systemd1', '/org/freedesktop/systemd1', 'org.freedesktop.DBus.Properties', None) - - # Note that the firmware/loader times are returned as positive - # values but are actually considered negative from the point - # in time of kernel initialization. Also, the monotonic kernel - # time will always be 0 since that's the epoch of the - # monotonic clock. Since we want to know whether the kernel - # timestamp is set at all we will instead ask for the realtime - # clock for this timestamp. - - firmware_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'FirmwareTimestampMonotonic') - loader_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'LoaderTimestampMonotonic') - kernel_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'KernelTimestamp') - initrd_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'InitRDTimestampMonotonic') - userspace_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'UserspaceTimestampMonotonic') - finish_time = properties.Get('(ss)', 'org.freedesktop.systemd1.Manager', 'FinishTimestampMonotonic') - - if finish_time == 0: - sys.exit("Bootup is not yet finished. Please try again later.") - - assert firmware_time >= loader_time - assert initrd_time <= userspace_time - assert userspace_time <= finish_time - - return firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time - -def draw_box(context, j, k, l, m, r = 0, g = 0, b = 0): - context.save() - context.set_source_rgb(r, g, b) - context.rectangle(j, k, l, m) - context.fill() - context.restore() - -def draw_text(context, x, y, text, size = 12, r = 0, g = 0, b = 0, vcenter = 0.5, hcenter = 0.5): - context.save() - - context.set_source_rgb(r, g, b) - context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) - context.set_font_size(size) - - if vcenter or hcenter: - x_bearing, y_bearing, width, height = context.text_extents(text)[:4] - - if hcenter: - x = x - width*hcenter - x_bearing - - if vcenter: - y = y - height*vcenter - y_bearing - - context.move_to(x, y) - context.show_text(text) - - context.restore() - -def time(): - - firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time = acquire_start_time() - - sys.stdout.write("Startup finished in ") - - if firmware_time > 0: - sys.stdout.write("%lums (firmware) + " % ((firmware_time - loader_time) / 1000)) - if loader_time > 0: - sys.stdout.write("%lums (loader) + " % (loader_time / 1000)) - if initrd_time > 0: - sys.stdout.write("%lums (kernel) + %lums (initrd) + " % (initrd_time / 1000, (userspace_time - initrd_time) / 1000)) - elif kernel_time > 0: - sys.stdout.write("%lums (kernel) + " % (userspace_time / 1000)) - - sys.stdout.write("%lums (userspace) " % ((finish_time - userspace_time) / 1000)) - - if kernel_time > 0: - sys.stdout.write("= %lums\n" % ((firmware_time + finish_time) / 1000)) - else: - sys.stdout.write("= %lums\n" % ((finish_time - userspace_time) / 1000)) - -def blame(): - - data = acquire_time_data() - s = sorted(data, key = lambda i: i[2] - i[1], reverse = True) - - for name, ixt, aet, axt, iet in s: - - if ixt <= 0 or aet <= 0: - continue - - if aet <= ixt: - continue - - sys.stdout.write("%6lums %s\n" % ((aet - ixt) / 1000, name)) - -def plot(): - if cairo is None: - sys.exit("Failed to initilize python-cairo required for 'plot' verb.") - firmware_time, loader_time, kernel_time, initrd_time, userspace_time, finish_time = acquire_start_time() - data = acquire_time_data() - s = sorted(data, key = lambda i: i[1]) - - # Account for kernel and initramfs bars if they exist - if initrd_time > 0: - count = 3 - else: - count = 2 - - for name, ixt, aet, axt, iet in s: - - if (ixt >= userspace_time and ixt <= finish_time) or \ - (aet >= userspace_time and aet <= finish_time) or \ - (axt >= userspace_time and axt <= finish_time): - count += 1 - - border = 100 - bar_height = 20 - bar_space = bar_height * 0.1 - - # 1000px = 10s, 1px = 10ms - width = finish_time/10000 + border*2 - height = count * (bar_height + bar_space) + border * 2 - - if width < 1000: - width = 1000 - - surface = cairo.SVGSurface(sys.stdout, width, height) - context = cairo.Context(surface) - - draw_box(context, 0, 0, width, height, 1, 1, 1) - - context.translate(border + 0.5, border + 0.5) - - context.save() - context.set_line_width(1) - context.set_source_rgb(0.7, 0.7, 0.7) - - for x in range(0, int(finish_time/10000) + 100, 100): - context.move_to(x, 0) - context.line_to(x, height-border*2) - - context.move_to(0, 0) - context.line_to(width-border*2, 0) - - context.move_to(0, height-border*2) - context.line_to(width-border*2, height-border*2) - - context.stroke() - context.restore() - - osrel = "Linux" - if os.path.exists("/etc/os-release"): - for line in open("/etc/os-release"): - if line.startswith('PRETTY_NAME='): - osrel = line[12:] - osrel = osrel.strip('\"\n') - break - - banner = "{} {} ({} {}) {}".format(osrel, *(os.uname()[1:5])) - draw_text(context, 0, -15, banner, hcenter = 0, vcenter = 1) - - for x in range(0, int(finish_time/10000) + 100, 100): - draw_text(context, x, -5, "%lus" % (x/100), vcenter = 0, hcenter = 0) - - y = 0 - - # draw boxes for kernel and initramfs boot time - if initrd_time > 0: - draw_box(context, 0, y, initrd_time/10000, bar_height, 0.7, 0.7, 0.7) - draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0) - y += bar_height + bar_space - - draw_box(context, initrd_time/10000, y, userspace_time/10000-initrd_time/10000, bar_height, 0.7, 0.7, 0.7) - draw_text(context, initrd_time/10000 + 10, y + bar_height/2, "initramfs", hcenter = 0) - y += bar_height + bar_space - - else: - draw_box(context, 0, y, userspace_time/10000, bar_height, 0.6, 0.6, 0.6) - draw_text(context, 10, y + bar_height/2, "kernel", hcenter = 0) - y += bar_height + bar_space - - draw_box(context, userspace_time/10000, y, finish_time/10000-userspace_time/10000, bar_height, 0.7, 0.7, 0.7) - draw_text(context, userspace_time/10000 + 10, y + bar_height/2, "userspace", hcenter = 0) - y += bar_height + bar_space - - for name, ixt, aet, axt, iet in s: - - drawn = False - left = -1 - - if ixt >= userspace_time and ixt <= finish_time: - - # Activating - a = ixt - b = min(filter(lambda x: x >= ixt, (aet, axt, iet, finish_time))) - ixt - - draw_box(context, a/10000, y, b/10000, bar_height, 1, 0, 0) - drawn = True - - if left < 0: - left = a - - if aet >= userspace_time and aet <= finish_time: - - # Active - a = aet - b = min(filter(lambda x: x >= aet, (axt, iet, finish_time))) - aet - - draw_box(context, a/10000, y, b/10000, bar_height, .8, .6, .6) - drawn = True - - if left < 0: - left = a - - if axt >= userspace_time and axt <= finish_time: - - # Deactivating - a = axt - b = min(filter(lambda x: x >= axt, (iet, finish_time))) - axt - - draw_box(context, a/10000, y, b/10000, bar_height, .6, .4, .4) - drawn = True - - if left < 0: - left = a - - if drawn: - x = left/10000 - - if x < width/2-border: - draw_text(context, x + 10, y + bar_height/2, name, hcenter = 0) - else: - draw_text(context, x - 10, y + bar_height/2, name, hcenter = 1) - - y += bar_height + bar_space - - draw_text(context, 0, height-border*2, "Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating", hcenter = 0, vcenter = -1) - - if initrd_time > 0: - draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (initramfs) + %lums (userspace) = %lums" % ( \ - initrd_time/1000, \ - (userspace_time - initrd_time)/1000, \ - (finish_time - userspace_time)/1000, \ - finish_time/1000), hcenter = 0, vcenter = -1) - else: - draw_text(context, 0, height-border*2 + bar_height, "Startup finished in %lums (kernel) + %lums (userspace) = %lums" % ( \ - userspace_time/1000, \ - (finish_time - userspace_time)/1000, \ - finish_time/1000), hcenter = 0, vcenter = -1) - - surface.finish() - -parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, - version='systemd-analyze @PACKAGE_VERSION@', - description='Process systemd profiling information', - epilog='''\ -time - print time spent in the kernel before reaching userspace -blame - print list of running units ordered by time to init -plot - output SVG graphic showing service initialization -''') - -parser.add_argument('action', choices=('time', 'blame', 'plot'), - default='time', nargs='?', - help='action to perform (default: time)') -parser.add_argument('--user', action='store_true', - help='use the session bus') - -args = parser.parse_args() - -if args.user: - bus = Gio.BusType.SESSION -else: - bus = Gio.BusType.SYSTEM - -verb = {'time' : time, - 'blame': blame, - 'plot' : plot, - } -verb.get(args.action)() -- 1.8.1.1