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>
32 #include "dbus-common.h"
38 #include "unit-name.h"
42 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
45 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
47 #define svg(...) printf(__VA_ARGS__)
49 #define svg_bar(class, x1, x2, y) \
50 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
52 SCALE_X * (x1), SCALE_Y * (y), \
53 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
55 #define svg_text(b, x, y, format, ...) \
57 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
58 svg(format, ## __VA_ARGS__); \
62 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
68 static char** arg_dot_from_patterns = NULL;
69 static char** arg_dot_to_patterns = NULL;
77 usec_t kernel_done_time;
79 usec_t userspace_time;
91 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
92 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
93 DBusMessageIter iter, sub;
96 r = bus_method_call_with_reply(
98 "org.freedesktop.systemd1",
100 "org.freedesktop.DBus.Properties",
104 DBUS_TYPE_STRING, &interface,
105 DBUS_TYPE_STRING, &property,
110 if (!dbus_message_iter_init(reply, &iter) ||
111 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
112 log_error("Failed to parse reply.");
116 dbus_message_iter_recurse(&iter, &sub);
118 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
119 log_error("Failed to parse reply.");
123 dbus_message_iter_get_basic(&sub, val);
128 static int compare_unit_time(const void *a, const void *b) {
129 return compare(((struct unit_times *)b)->time,
130 ((struct unit_times *)a)->time);
133 static int compare_unit_start(const void *a, const void *b) {
134 return compare(((struct unit_times *)a)->ixt,
135 ((struct unit_times *)b)->ixt);
138 static int get_os_name(char **_n) {
142 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
153 static void free_unit_times(struct unit_times *t, unsigned n) {
154 struct unit_times *p;
156 for (p = t; p < t + n; p++)
162 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
163 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
164 DBusMessageIter iter, sub;
165 int r, c = 0, n_units = 0;
166 struct unit_times *unit_times = NULL;
168 r = bus_method_call_with_reply(
170 "org.freedesktop.systemd1",
171 "/org/freedesktop/systemd1",
172 "org.freedesktop.systemd1.Manager",
180 if (!dbus_message_iter_init(reply, &iter) ||
181 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
182 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
183 log_error("Failed to parse reply.");
188 for (dbus_message_iter_recurse(&iter, &sub);
189 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
190 dbus_message_iter_next(&sub)) {
192 struct unit_times *t;
194 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
195 log_error("Failed to parse reply.");
201 struct unit_times *w;
203 n_units = MAX(2*c, 16);
204 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
216 r = bus_parse_unit_info(&sub, &u);
220 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
222 if (bus_get_uint64_property(bus, u.unit_path,
223 "org.freedesktop.systemd1.Unit",
224 "InactiveExitTimestampMonotonic",
226 bus_get_uint64_property(bus, u.unit_path,
227 "org.freedesktop.systemd1.Unit",
228 "ActiveEnterTimestampMonotonic",
230 bus_get_uint64_property(bus, u.unit_path,
231 "org.freedesktop.systemd1.Unit",
232 "ActiveExitTimestampMonotonic",
234 bus_get_uint64_property(bus, u.unit_path,
235 "org.freedesktop.systemd1.Unit",
236 "InactiveEnterTimestampMonotonic",
242 if (t->aet >= t->ixt)
243 t->time = t->aet - t->ixt;
244 else if (t->iet >= t->ixt)
245 t->time = t->iet - t->ixt;
252 t->name = strdup(u.id);
253 if (t->name == NULL) {
264 free_unit_times(unit_times, (unsigned) c);
268 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
269 static struct boot_times times;
270 static bool cached = false;
275 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
277 if (bus_get_uint64_property(bus,
278 "/org/freedesktop/systemd1",
279 "org.freedesktop.systemd1.Manager",
280 "FirmwareTimestampMonotonic",
281 ×.firmware_time) < 0 ||
282 bus_get_uint64_property(bus,
283 "/org/freedesktop/systemd1",
284 "org.freedesktop.systemd1.Manager",
285 "LoaderTimestampMonotonic",
286 ×.loader_time) < 0 ||
287 bus_get_uint64_property(bus,
288 "/org/freedesktop/systemd1",
289 "org.freedesktop.systemd1.Manager",
291 ×.kernel_time) < 0 ||
292 bus_get_uint64_property(bus,
293 "/org/freedesktop/systemd1",
294 "org.freedesktop.systemd1.Manager",
295 "InitRDTimestampMonotonic",
296 ×.initrd_time) < 0 ||
297 bus_get_uint64_property(bus,
298 "/org/freedesktop/systemd1",
299 "org.freedesktop.systemd1.Manager",
300 "UserspaceTimestampMonotonic",
301 ×.userspace_time) < 0 ||
302 bus_get_uint64_property(bus,
303 "/org/freedesktop/systemd1",
304 "org.freedesktop.systemd1.Manager",
305 "FinishTimestampMonotonic",
306 ×.finish_time) < 0)
309 if (times.finish_time <= 0) {
310 log_error("Bootup is not yet finished. Please try again later.");
314 if (times.initrd_time)
315 times.kernel_done_time = times.initrd_time;
317 times.kernel_done_time = times.userspace_time;
326 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
327 char ts[FORMAT_TIMESPAN_MAX];
328 struct boot_times *t;
329 static char buf[4096];
334 r = acquire_boot_times(bus, &t);
341 size = strpcpyf(&ptr, size, "Startup finished in ");
342 if (t->firmware_time)
343 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
345 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
347 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
348 if (t->initrd_time > 0)
349 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
351 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
352 if (t->kernel_time > 0)
353 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
355 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
365 static void svg_graph_box(double height, double begin, double end) {
368 /* outside box, fill */
369 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
370 SCALE_X * (end - begin), SCALE_Y * height);
372 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
373 /* lines for each second */
374 if (i % 5000000 == 0)
375 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
376 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
377 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
378 else if (i % 1000000 == 0)
379 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
380 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
381 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
383 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
384 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
388 static int analyze_plot(DBusConnection *bus) {
389 struct unit_times *times;
390 struct boot_times *boot;
394 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
395 struct unit_times *u;
397 n = acquire_boot_times(bus, &boot);
401 n = pretty_boot_time(bus, &pretty_times);
405 get_os_name(&osname);
406 assert_se(uname(&name) >= 0);
408 n = acquire_time_data(bus, ×);
412 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
414 width = SCALE_X * (boot->firmware_time + boot->finish_time);
418 if (boot->firmware_time > boot->loader_time)
420 if (boot->loader_time) {
425 if (boot->initrd_time)
427 if (boot->kernel_time)
430 for (u = times; u < times + n; u++) {
433 if (u->ixt < boot->userspace_time ||
434 u->ixt > boot->finish_time) {
439 len = ((boot->firmware_time + u->ixt) * SCALE_X)
440 + (10.0 * strlen(u->name));
444 if (u->iet > u->ixt && u->iet <= boot->finish_time
445 && u->aet == 0 && u->axt == 0)
446 u->aet = u->axt = u->iet;
447 if (u->aet < u->ixt || u->aet > boot->finish_time)
448 u->aet = boot->finish_time;
449 if (u->axt < u->aet || u->aet > boot->finish_time)
450 u->axt = boot->finish_time;
451 if (u->iet < u->axt || u->iet > boot->finish_time)
452 u->iet = boot->finish_time;
456 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
457 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
458 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
460 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
461 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
462 80.0 + width, 150.0 + (m * SCALE_Y));
464 /* write some basic info as a comment, including some help */
465 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
466 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
467 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
468 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
469 "<!-- point your browser to this file. -->\n\n"
470 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
473 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
474 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
475 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
476 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
477 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
478 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
479 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
480 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
481 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
482 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
483 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
484 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
486 " line.sec5 { stroke-width: 2; }\n"
487 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
488 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
489 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
490 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
491 " text.sec { font-size: 8; }\n"
492 " ]]>\n </style>\n</defs>\n\n");
494 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
495 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
496 isempty(osname) ? "Linux" : osname,
497 name.nodename, name.release, name.version, name.machine);
498 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
499 120.0 + (m *SCALE_Y));
501 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
502 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
504 if (boot->firmware_time) {
505 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
506 svg_text(true, -(double) boot->firmware_time, y, "firmware");
509 if (boot->loader_time) {
510 svg_bar("loader", -(double) boot->loader_time, 0, y);
511 svg_text(true, -(double) boot->loader_time, y, "loader");
514 if (boot->kernel_time) {
515 svg_bar("kernel", 0, boot->kernel_done_time, y);
516 svg_text(true, 0, y, "kernel");
519 if (boot->initrd_time) {
520 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
521 svg_text(true, boot->initrd_time, y, "initrd");
524 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
525 svg_text("left", boot->userspace_time, y, "userspace");
528 for (u = times; u < times + n; u++) {
529 char ts[FORMAT_TIMESPAN_MAX];
535 svg_bar("activating", u->ixt, u->aet, y);
536 svg_bar("active", u->aet, u->axt, y);
537 svg_bar("deactivating", u->axt, u->iet, y);
539 b = u->ixt * SCALE_X > width * 2 / 3;
541 svg_text(b, u->ixt, y, "%s (%s)",
542 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
544 svg_text(b, u->ixt, y, "%s", u->name);
551 free_unit_times(times, (unsigned) n);
557 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
558 bool last, struct unit_times *times, struct boot_times *boot) {
560 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
562 for (i = level; i != 0; i--)
563 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
565 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
569 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
570 format_timespan(ts, sizeof(ts), times->ixt - boot->userspace_time, USEC_PER_MSEC),
571 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
572 else if (times->aet > boot->userspace_time)
573 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
576 } else printf("%s", name);
582 static int list_dependencies_get_dependencies(DBusConnection *bus, const char *name, char ***deps) {
583 static const char dependencies[] =
586 _cleanup_free_ char *path;
587 const char *interface = "org.freedesktop.systemd1.Unit";
589 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
590 DBusMessageIter iter, sub, sub2, sub3;
599 path = unit_dbus_path_from_name(name);
605 r = bus_method_call_with_reply(
607 "org.freedesktop.systemd1",
609 "org.freedesktop.DBus.Properties",
613 DBUS_TYPE_STRING, &interface,
618 if (!dbus_message_iter_init(reply, &iter) ||
619 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
620 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
621 log_error("Failed to parse reply.");
626 dbus_message_iter_recurse(&iter, &sub);
628 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
631 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
632 dbus_message_iter_recurse(&sub, &sub2);
634 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
635 log_error("Failed to parse reply.");
640 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
641 log_error("Failed to parse reply.");
646 dbus_message_iter_recurse(&sub2, &sub3);
647 dbus_message_iter_next(&sub);
649 if (!nulstr_contains(dependencies, prop))
652 if (dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_ARRAY) {
653 if (dbus_message_iter_get_element_type(&sub3) == DBUS_TYPE_STRING) {
654 DBusMessageIter sub4;
655 dbus_message_iter_recurse(&sub3, &sub4);
657 while (dbus_message_iter_get_arg_type(&sub4) != DBUS_TYPE_INVALID) {
660 assert(dbus_message_iter_get_arg_type(&sub4) == DBUS_TYPE_STRING);
661 dbus_message_iter_get_basic(&sub4, &s);
663 r = strv_extend(&ret, s);
669 dbus_message_iter_next(&sub4);
682 static Hashmap *unit_times_hashmap;
684 static int list_dependencies_compare(const void *_a, const void *_b) {
685 const char **a = (const char**) _a, **b = (const char**) _b;
686 usec_t usa = 0, usb = 0;
687 struct unit_times *times;
689 times = hashmap_get(unit_times_hashmap, *a);
692 times = hashmap_get(unit_times_hashmap, *b);
699 static int list_dependencies_one(DBusConnection *bus, const char *name, unsigned int level, char ***units,
700 unsigned int branches) {
701 _cleanup_strv_free_ char **deps = NULL;
704 usec_t service_longest = 0;
706 struct unit_times *times;
707 struct boot_times *boot;
709 if(strv_extend(units, name))
712 r = list_dependencies_get_dependencies(bus, name, &deps);
716 qsort(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
718 r = acquire_boot_times(bus, &boot);
722 STRV_FOREACH(c, deps) {
723 times = hashmap_get(unit_times_hashmap, *c);
726 && times->aet <= boot->finish_time
727 && (times->aet >= service_longest
728 || service_longest == 0)) {
729 service_longest = times->aet;
734 if (service_longest == 0 )
737 STRV_FOREACH(c, deps) {
738 times = hashmap_get(unit_times_hashmap, *c);
739 if (times && times->aet
740 && times->aet <= boot->finish_time
741 && (service_longest - times->aet) <= arg_fuzz) {
749 STRV_FOREACH(c, deps) {
750 times = hashmap_get(unit_times_hashmap, *c);
753 || times->aet > boot->finish_time
754 || service_longest - times->aet > arg_fuzz)
759 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
763 if (strv_contains(*units, *c)) {
764 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
769 r = list_dependencies_one(bus, *c, level + 1, units,
770 (branches << 1) | (to_print ? 1 : 0));
782 static int list_dependencies(DBusConnection *bus) {
783 _cleanup_strv_free_ char **units = NULL;
784 char ts[FORMAT_TIMESPAN_MAX];
785 struct unit_times *times;
789 *interface = "org.freedesktop.systemd1.Unit",
791 DBusMessageIter iter, sub;
792 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
793 struct boot_times *boot;
797 path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
801 r = bus_method_call_with_reply (
803 "org.freedesktop.systemd1",
805 "org.freedesktop.DBus.Properties",
809 DBUS_TYPE_STRING, &interface,
810 DBUS_TYPE_STRING, &property,
815 if (!dbus_message_iter_init(reply, &iter) ||
816 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
817 log_error("Failed to parse reply.");
821 dbus_message_iter_recurse(&iter, &sub);
823 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
824 log_error("Failed to parse reply.");
828 dbus_message_iter_get_basic(&sub, &id);
830 times = hashmap_get(unit_times_hashmap, id);
832 r = acquire_boot_times(bus, &boot);
838 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
839 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
840 else if (times->aet > boot->userspace_time)
841 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
846 return list_dependencies_one(bus, SPECIAL_DEFAULT_TARGET, 0, &units, 0);
849 static int analyze_critical_chain(DBusConnection *bus) {
850 struct unit_times *times;
855 n = acquire_time_data(bus, ×);
859 h = hashmap_new(string_hash_func, string_compare_func);
863 for (i = 0; i < (unsigned)n; i++) {
864 r = hashmap_put(h, times[i].name, ×[i]);
868 unit_times_hashmap = h;
870 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
871 "The time the unit takes to start is printed after the \"+\" character.\n");
873 list_dependencies(bus);
876 free_unit_times(times, (unsigned) n);
880 static int analyze_blame(DBusConnection *bus) {
881 struct unit_times *times;
885 n = acquire_time_data(bus, ×);
889 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
891 for (i = 0; i < (unsigned) n; i++) {
892 char ts[FORMAT_TIMESPAN_MAX];
894 if (times[i].time > 0)
895 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
898 free_unit_times(times, (unsigned) n);
902 static int analyze_time(DBusConnection *bus) {
903 _cleanup_free_ char *buf = NULL;
906 r = pretty_boot_time(bus, &buf);
914 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
916 static const char * const colors[] = {
917 "Requires", "[color=\"black\"]",
918 "RequiresOverridable", "[color=\"black\"]",
919 "Requisite", "[color=\"darkblue\"]",
920 "RequisiteOverridable", "[color=\"darkblue\"]",
921 "Wants", "[color=\"grey66\"]",
922 "Conflicts", "[color=\"red\"]",
923 "ConflictedBy", "[color=\"red\"]",
924 "After", "[color=\"green\"]"
927 const char *c = NULL;
934 for (i = 0; i < ELEMENTSOF(colors); i += 2)
935 if (streq(colors[i], prop)) {
943 if (arg_dot != DEP_ALL)
944 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
947 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
948 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
951 dbus_message_iter_recurse(iter, &sub);
953 for (dbus_message_iter_recurse(iter, &sub);
954 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
955 dbus_message_iter_next(&sub)) {
960 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
961 dbus_message_iter_get_basic(&sub, &s);
963 if (!strv_isempty(arg_dot_from_patterns)) {
966 STRV_FOREACH(p, arg_dot_from_patterns)
967 if (fnmatch(*p, name, 0) == 0) {
976 if (!strv_isempty(arg_dot_to_patterns)) {
979 STRV_FOREACH(p, arg_dot_to_patterns)
980 if (fnmatch(*p, s, 0) == 0) {
989 if (!strv_isempty(patterns)) {
992 STRV_FOREACH(p, patterns)
993 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
1001 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
1008 static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
1009 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1010 const char *interface = "org.freedesktop.systemd1.Unit";
1012 DBusMessageIter iter, sub, sub2, sub3;
1017 r = bus_method_call_with_reply(
1019 "org.freedesktop.systemd1",
1021 "org.freedesktop.DBus.Properties",
1025 DBUS_TYPE_STRING, &interface,
1030 if (!dbus_message_iter_init(reply, &iter) ||
1031 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1032 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
1033 log_error("Failed to parse reply.");
1037 for (dbus_message_iter_recurse(&iter, &sub);
1038 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1039 dbus_message_iter_next(&sub)) {
1042 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
1043 dbus_message_iter_recurse(&sub, &sub2);
1045 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
1046 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
1047 log_error("Failed to parse reply.");
1051 dbus_message_iter_recurse(&sub2, &sub3);
1052 r = graph_one_property(u->id, prop, &sub3, patterns);
1060 static int dot(DBusConnection *bus, char* patterns[]) {
1061 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1062 DBusMessageIter iter, sub;
1065 r = bus_method_call_with_reply(
1067 "org.freedesktop.systemd1",
1068 "/org/freedesktop/systemd1",
1069 "org.freedesktop.systemd1.Manager",
1077 if (!dbus_message_iter_init(reply, &iter) ||
1078 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1079 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
1080 log_error("Failed to parse reply.");
1084 printf("digraph systemd {\n");
1086 for (dbus_message_iter_recurse(&iter, &sub);
1087 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1088 dbus_message_iter_next(&sub)) {
1091 r = bus_parse_unit_info(&sub, &u);
1095 r = graph_one(bus, &u, patterns);
1102 log_info(" Color legend: black = Requires\n"
1103 " dark blue = Requisite\n"
1104 " dark grey = Wants\n"
1105 " red = Conflicts\n"
1106 " green = After\n");
1109 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1110 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1115 static void analyze_help(void)
1117 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1118 "Process systemd profiling information\n\n"
1119 " -h --help Show this help\n"
1120 " --version Show package version\n"
1121 " --system Connect to system manager\n"
1122 " --user Connect to user service manager\n"
1123 " --order When generating a dependency graph, show only order\n"
1124 " --require When generating a dependency graph, show only requirement\n"
1125 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1126 " When generating a dependency graph, filter only origins\n"
1127 " or destinations, respectively\n"
1128 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1129 " services, which finished TIMESPAN earlier, than the\n"
1130 " latest in the branch. The unit of TIMESPAN is seconds\n"
1131 " unless specified with a different unit, i.e. 50ms\n\n"
1133 " time Print time spent in the kernel before reaching userspace\n"
1134 " blame Print list of running units ordered by time to init\n"
1135 " critical-chain Print a tree of the time critical chain of units\n"
1136 " plot Output SVG graphic showing service initialization\n"
1137 " dot Dump dependency graph (in dot(1) format)\n\n",
1138 program_invocation_short_name);
1140 /* When updating this list, including descriptions, apply
1141 * changes to shell-completion/bash/systemd and
1142 * shell-completion/systemd-zsh-completion.zsh too. */
1145 static int parse_argv(int argc, char *argv[])
1150 ARG_VERSION = 0x100,
1155 ARG_DOT_FROM_PATTERN,
1160 static const struct option options[] = {
1161 { "help", no_argument, NULL, 'h' },
1162 { "version", no_argument, NULL, ARG_VERSION },
1163 { "order", no_argument, NULL, ARG_ORDER },
1164 { "require", no_argument, NULL, ARG_REQUIRE },
1165 { "user", no_argument, NULL, ARG_USER },
1166 { "system", no_argument, NULL, ARG_SYSTEM },
1167 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
1168 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1169 { "fuzz", required_argument, NULL, ARG_FUZZ },
1170 { NULL, 0, NULL, 0 }
1177 switch (getopt_long(argc, argv, "h", options, NULL)) {
1184 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1188 arg_scope = UNIT_FILE_USER;
1192 arg_scope = UNIT_FILE_SYSTEM;
1196 arg_dot = DEP_ORDER;
1200 arg_dot = DEP_REQUIRE;
1203 case ARG_DOT_FROM_PATTERN:
1204 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1209 case ARG_DOT_TO_PATTERN:
1210 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1216 r = parse_sec(optarg, &arg_fuzz);
1228 assert_not_reached("Unhandled option");
1233 int main(int argc, char *argv[]) {
1235 DBusConnection *bus = NULL;
1237 setlocale(LC_ALL, "");
1238 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1239 log_parse_environment();
1242 r = parse_argv(argc, argv);
1244 return EXIT_FAILURE;
1246 return EXIT_SUCCESS;
1248 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
1250 return EXIT_FAILURE;
1252 if (!argv[optind] || streq(argv[optind], "time"))
1253 r = analyze_time(bus);
1254 else if (streq(argv[optind], "blame"))
1255 r = analyze_blame(bus);
1256 else if (streq(argv[optind], "critical-chain"))
1257 r = analyze_critical_chain(bus);
1258 else if (streq(argv[optind], "plot"))
1259 r = analyze_plot(bus);
1260 else if (streq(argv[optind], "dot"))
1261 r = dot(bus, argv+optind+1);
1263 log_error("Unknown operation '%s'.", argv[optind]);
1265 strv_free(arg_dot_from_patterns);
1266 strv_free(arg_dot_to_patterns);
1267 dbus_connection_unref(bus);
1269 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;