1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010-2013 Lennart Poettering
7 Copyright 2013 Simon Peeters
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
27 #include <sys/utsname.h>
31 #include "dbus-common.h"
37 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
40 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
42 #define svg(...) printf(__VA_ARGS__)
44 #define svg_bar(class, x1, x2, y) \
45 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
47 SCALE_X * (x1), SCALE_Y * (y), \
48 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
50 #define svg_text(b, x, y, format, ...) \
52 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
53 svg(format, ## __VA_ARGS__); \
57 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
68 usec_t kernel_done_time;
70 usec_t userspace_time;
82 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
83 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
84 DBusMessageIter iter, sub;
87 r = bus_method_call_with_reply(
89 "org.freedesktop.systemd1",
91 "org.freedesktop.DBus.Properties",
95 DBUS_TYPE_STRING, &interface,
96 DBUS_TYPE_STRING, &property,
101 if (!dbus_message_iter_init(reply, &iter) ||
102 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
103 log_error("Failed to parse reply.");
107 dbus_message_iter_recurse(&iter, &sub);
109 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
110 log_error("Failed to parse reply.");
114 dbus_message_iter_get_basic(&sub, val);
119 static int compare_unit_time(const void *a, const void *b) {
120 return compare(((struct unit_times *)b)->time,
121 ((struct unit_times *)a)->time);
124 static int compare_unit_start(const void *a, const void *b) {
125 return compare(((struct unit_times *)a)->ixt,
126 ((struct unit_times *)b)->ixt);
129 static int get_os_name(char **_n) {
133 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
144 static void free_unit_times(struct unit_times *t, unsigned n) {
145 struct unit_times *p;
147 for (p = t; p < t + n; p++)
153 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
154 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
155 DBusMessageIter iter, sub;
156 int r, c = 0, n_units = 0;
157 struct unit_times *unit_times = NULL;
159 r = bus_method_call_with_reply(
161 "org.freedesktop.systemd1",
162 "/org/freedesktop/systemd1",
163 "org.freedesktop.systemd1.Manager",
171 if (!dbus_message_iter_init(reply, &iter) ||
172 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
173 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
174 log_error("Failed to parse reply.");
179 for (dbus_message_iter_recurse(&iter, &sub);
180 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
181 dbus_message_iter_next(&sub)) {
183 struct unit_times *t;
185 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
186 log_error("Failed to parse reply.");
192 struct unit_times *w;
194 n_units = MAX(2*c, 16);
195 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
207 r = bus_parse_unit_info(&sub, &u);
211 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
213 if (bus_get_uint64_property(bus, u.unit_path,
214 "org.freedesktop.systemd1.Unit",
215 "InactiveExitTimestampMonotonic",
217 bus_get_uint64_property(bus, u.unit_path,
218 "org.freedesktop.systemd1.Unit",
219 "ActiveEnterTimestampMonotonic",
221 bus_get_uint64_property(bus, u.unit_path,
222 "org.freedesktop.systemd1.Unit",
223 "ActiveExitTimestampMonotonic",
225 bus_get_uint64_property(bus, u.unit_path,
226 "org.freedesktop.systemd1.Unit",
227 "InactiveEnterTimestampMonotonic",
233 if (t->aet >= t->ixt)
234 t->time = t->aet - t->ixt;
235 else if (t->iet >= t->ixt)
236 t->time = t->iet - t->ixt;
243 t->name = strdup(u.id);
244 if (t->name == NULL) {
255 free_unit_times(unit_times, (unsigned) c);
259 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
260 static struct boot_times times;
261 static bool cached = false;
266 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
268 if (bus_get_uint64_property(bus,
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "FirmwareTimestampMonotonic",
272 ×.firmware_time) < 0 ||
273 bus_get_uint64_property(bus,
274 "/org/freedesktop/systemd1",
275 "org.freedesktop.systemd1.Manager",
276 "LoaderTimestampMonotonic",
277 ×.loader_time) < 0 ||
278 bus_get_uint64_property(bus,
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
282 ×.kernel_time) < 0 ||
283 bus_get_uint64_property(bus,
284 "/org/freedesktop/systemd1",
285 "org.freedesktop.systemd1.Manager",
286 "InitRDTimestampMonotonic",
287 ×.initrd_time) < 0 ||
288 bus_get_uint64_property(bus,
289 "/org/freedesktop/systemd1",
290 "org.freedesktop.systemd1.Manager",
291 "UserspaceTimestampMonotonic",
292 ×.userspace_time) < 0 ||
293 bus_get_uint64_property(bus,
294 "/org/freedesktop/systemd1",
295 "org.freedesktop.systemd1.Manager",
296 "FinishTimestampMonotonic",
297 ×.finish_time) < 0)
300 if (times.finish_time <= 0) {
301 log_error("Bootup is not yet finished. Please try again later.");
305 if (times.initrd_time)
306 times.kernel_done_time = times.initrd_time;
308 times.kernel_done_time = times.userspace_time;
317 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
318 char ts[FORMAT_TIMESPAN_MAX];
319 struct boot_times *t;
320 static char buf[4096];
325 r = acquire_boot_times(bus, &t);
332 size = strpcpyf(&ptr, size, "Startup finished in ");
333 if (t->firmware_time)
334 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time));
336 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time));
338 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time));
339 if (t->initrd_time > 0)
340 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time));
342 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
343 if (t->kernel_time > 0)
344 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time));
346 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
356 static void svg_graph_box(double height, double begin, double end) {
359 /* outside box, fill */
360 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
361 SCALE_X * (end - begin), SCALE_Y * height);
363 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
364 /* lines for each second */
365 if (i % 5000000 == 0)
366 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
367 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
368 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
369 else if (i % 1000000 == 0)
370 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
371 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
372 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
374 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
375 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
379 static int analyze_plot(DBusConnection *bus) {
380 struct unit_times *times;
381 struct boot_times *boot;
385 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
386 struct unit_times *u;
388 n = acquire_boot_times(bus, &boot);
392 n = pretty_boot_time(bus, &pretty_times);
396 get_os_name(&osname);
397 assert_se(uname(&name) >= 0);
399 n = acquire_time_data(bus, ×);
403 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
405 width = SCALE_X * (boot->firmware_time + boot->finish_time);
409 if (boot->firmware_time > boot->loader_time)
411 if (boot->loader_time) {
416 if (boot->initrd_time)
418 if (boot->kernel_time)
421 for (u = times; u < times + n; u++) {
424 if (u->ixt < boot->userspace_time ||
425 u->ixt > boot->finish_time) {
430 len = ((boot->firmware_time + u->ixt) * SCALE_X)
431 + (10.0 * strlen(u->name));
435 if (u->iet > u->ixt && u->iet <= boot->finish_time
436 && u->aet == 0 && u->axt == 0)
437 u->aet = u->axt = u->iet;
438 if (u->aet < u->ixt || u->aet > boot->finish_time)
439 u->aet = boot->finish_time;
440 if (u->axt < u->aet || u->aet > boot->finish_time)
441 u->axt = boot->finish_time;
442 if (u->iet < u->axt || u->iet > boot->finish_time)
443 u->iet = boot->finish_time;
447 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
448 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
449 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
451 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
452 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
453 80.0 + width, 150.0 + (m * SCALE_Y));
455 /* write some basic info as a comment, including some help */
456 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
457 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
458 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
459 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
460 "<!-- point your browser to this file. -->\n\n"
461 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
464 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
465 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
466 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
467 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
468 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
469 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
470 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
471 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
472 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
473 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
474 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
475 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
477 " line.sec5 { stroke-width: 2; }\n"
478 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
479 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
480 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
481 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
482 " text.sec { font-size: 8; }\n"
483 " ]]>\n </style>\n</defs>\n\n");
485 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
486 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
487 isempty(osname) ? "Linux" : osname,
488 name.nodename, name.release, name.version, name.machine);
489 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
490 120.0 + (m *SCALE_Y));
492 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
493 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
495 if (boot->firmware_time) {
496 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
497 svg_text(true, -(double) boot->firmware_time, y, "firmware");
500 if (boot->loader_time) {
501 svg_bar("loader", -(double) boot->loader_time, 0, y);
502 svg_text(true, -(double) boot->loader_time, y, "loader");
505 if (boot->kernel_time) {
506 svg_bar("kernel", 0, boot->kernel_done_time, y);
507 svg_text(true, 0, y, "kernel");
510 if (boot->initrd_time) {
511 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
512 svg_text(true, boot->initrd_time, y, "initrd");
515 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
516 svg_text("left", boot->userspace_time, y, "userspace");
519 for (u = times; u < times + n; u++) {
520 char ts[FORMAT_TIMESPAN_MAX];
526 svg_bar("activating", u->ixt, u->aet, y);
527 svg_bar("active", u->aet, u->axt, y);
528 svg_bar("deactivating", u->axt, u->iet, y);
530 b = u->ixt * SCALE_X > width * 2 / 3;
532 svg_text(b, u->ixt, y, "%s (%s)",
533 u->name, format_timespan(ts, sizeof(ts), u->time));
535 svg_text(b, u->ixt, y, "%s", u->name);
542 free_unit_times(times, (unsigned) n);
547 static int analyze_blame(DBusConnection *bus) {
548 struct unit_times *times;
552 n = acquire_time_data(bus, ×);
556 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
558 for (i = 0; i < (unsigned) n; i++) {
559 char ts[FORMAT_TIMESPAN_MAX];
561 if (times[i].time > 0)
562 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time), times[i].name);
565 free_unit_times(times, (unsigned) n);
569 static int analyze_time(DBusConnection *bus) {
570 _cleanup_free_ char *buf = NULL;
573 r = pretty_boot_time(bus, &buf);
581 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
583 static const char * const colors[] = {
584 "Requires", "[color=\"black\"]",
585 "RequiresOverridable", "[color=\"black\"]",
586 "Requisite", "[color=\"darkblue\"]",
587 "RequisiteOverridable", "[color=\"darkblue\"]",
588 "Wants", "[color=\"grey66\"]",
589 "Conflicts", "[color=\"red\"]",
590 "ConflictedBy", "[color=\"red\"]",
591 "After", "[color=\"green\"]"
594 const char *c = NULL;
601 for (i = 0; i < ELEMENTSOF(colors); i += 2)
602 if (streq(colors[i], prop)) {
610 if (arg_dot != DEP_ALL)
611 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
614 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
615 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
618 dbus_message_iter_recurse(iter, &sub);
620 for (dbus_message_iter_recurse(iter, &sub);
621 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
622 dbus_message_iter_next(&sub)) {
625 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
626 dbus_message_iter_get_basic(&sub, &s);
627 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
634 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
635 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
636 const char *interface = "org.freedesktop.systemd1.Unit";
638 DBusMessageIter iter, sub, sub2, sub3;
643 r = bus_method_call_with_reply(
645 "org.freedesktop.systemd1",
647 "org.freedesktop.DBus.Properties",
651 DBUS_TYPE_STRING, &interface,
656 if (!dbus_message_iter_init(reply, &iter) ||
657 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
658 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
659 log_error("Failed to parse reply.");
663 for (dbus_message_iter_recurse(&iter, &sub);
664 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
665 dbus_message_iter_next(&sub)) {
668 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
669 dbus_message_iter_recurse(&sub, &sub2);
671 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
672 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
673 log_error("Failed to parse reply.");
677 dbus_message_iter_recurse(&sub2, &sub3);
678 r = graph_one_property(u->id, prop, &sub3);
686 static int dot(DBusConnection *bus) {
687 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
688 DBusMessageIter iter, sub;
691 r = bus_method_call_with_reply(
693 "org.freedesktop.systemd1",
694 "/org/freedesktop/systemd1",
695 "org.freedesktop.systemd1.Manager",
703 if (!dbus_message_iter_init(reply, &iter) ||
704 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
705 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
706 log_error("Failed to parse reply.");
710 printf("digraph systemd {\n");
712 for (dbus_message_iter_recurse(&iter, &sub);
713 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
714 dbus_message_iter_next(&sub)) {
717 r = bus_parse_unit_info(&sub, &u);
721 r = graph_one(bus, &u);
728 log_info(" Color legend: black = Requires\n"
729 " dark blue = Requisite\n"
730 " dark grey = Wants\n"
735 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
736 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
741 static void analyze_help(void)
743 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
744 "Process systemd profiling information\n\n"
745 " -h --help Show this help\n"
746 " --version Show package version\n"
747 " --system Connect to system manager\n"
748 " --user Connect to user service manager\n"
749 " --order When generating a dependency graph, show only order\n"
750 " --require When generating a dependency graph, show only requirement\n\n"
752 " time Print time spent in the kernel before reaching userspace\n"
753 " blame Print list of running units ordered by time to init\n"
754 " plot Output SVG graphic showing service initialization\n"
755 " dot Dump dependency graph (in dot(1) format)\n\n",
756 program_invocation_short_name);
759 static int parse_argv(int argc, char *argv[])
769 static const struct option options[] = {
770 { "help", no_argument, NULL, 'h' },
771 { "version", no_argument, NULL, ARG_VERSION },
772 { "order", no_argument, NULL, ARG_ORDER },
773 { "require", no_argument, NULL, ARG_REQUIRE },
774 { "user", no_argument, NULL, ARG_USER },
775 { "system", no_argument, NULL, ARG_SYSTEM },
783 switch (getopt_long(argc, argv, "h", options, NULL)) {
790 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
794 arg_scope = UNIT_FILE_USER;
798 arg_scope = UNIT_FILE_SYSTEM;
806 arg_dot = DEP_REQUIRE;
816 assert_not_reached("Unhandled option");
821 int main(int argc, char *argv[]) {
823 DBusConnection *bus = NULL;
825 setlocale(LC_ALL, "");
826 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
827 log_parse_environment();
830 r = parse_argv(argc, argv);
836 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
840 if (!argv[optind] || streq(argv[optind], "time"))
841 r = analyze_time(bus);
842 else if (streq(argv[optind], "blame"))
843 r = analyze_blame(bus);
844 else if (streq(argv[optind], "plot"))
845 r = analyze_plot(bus);
846 else if (streq(argv[optind], "dot"))
849 log_error("Unknown operation '%s'.", argv[optind]);
851 dbus_connection_unref(bus);
853 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;