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;
81 usec_t generators_start_time;
82 usec_t generators_finish_time;
93 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
94 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
95 DBusMessageIter iter, sub;
98 r = bus_method_call_with_reply(
100 "org.freedesktop.systemd1",
102 "org.freedesktop.DBus.Properties",
106 DBUS_TYPE_STRING, &interface,
107 DBUS_TYPE_STRING, &property,
112 if (!dbus_message_iter_init(reply, &iter) ||
113 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
114 log_error("Failed to parse reply.");
118 dbus_message_iter_recurse(&iter, &sub);
120 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
121 log_error("Failed to parse reply.");
125 dbus_message_iter_get_basic(&sub, val);
130 static int compare_unit_time(const void *a, const void *b) {
131 return compare(((struct unit_times *)b)->time,
132 ((struct unit_times *)a)->time);
135 static int compare_unit_start(const void *a, const void *b) {
136 return compare(((struct unit_times *)a)->ixt,
137 ((struct unit_times *)b)->ixt);
140 static int get_os_name(char **_n) {
144 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
155 static void free_unit_times(struct unit_times *t, unsigned n) {
156 struct unit_times *p;
158 for (p = t; p < t + n; p++)
164 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
165 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
166 DBusMessageIter iter, sub;
167 int r, c = 0, n_units = 0;
168 struct unit_times *unit_times = NULL;
170 r = bus_method_call_with_reply(
172 "org.freedesktop.systemd1",
173 "/org/freedesktop/systemd1",
174 "org.freedesktop.systemd1.Manager",
182 if (!dbus_message_iter_init(reply, &iter) ||
183 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
184 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
185 log_error("Failed to parse reply.");
190 for (dbus_message_iter_recurse(&iter, &sub);
191 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
192 dbus_message_iter_next(&sub)) {
194 struct unit_times *t;
196 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
197 log_error("Failed to parse reply.");
203 struct unit_times *w;
205 n_units = MAX(2*c, 16);
206 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
218 r = bus_parse_unit_info(&sub, &u);
222 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
224 if (bus_get_uint64_property(bus, u.unit_path,
225 "org.freedesktop.systemd1.Unit",
226 "InactiveExitTimestampMonotonic",
228 bus_get_uint64_property(bus, u.unit_path,
229 "org.freedesktop.systemd1.Unit",
230 "ActiveEnterTimestampMonotonic",
232 bus_get_uint64_property(bus, u.unit_path,
233 "org.freedesktop.systemd1.Unit",
234 "ActiveExitTimestampMonotonic",
236 bus_get_uint64_property(bus, u.unit_path,
237 "org.freedesktop.systemd1.Unit",
238 "InactiveEnterTimestampMonotonic",
244 if (t->aet >= t->ixt)
245 t->time = t->aet - t->ixt;
246 else if (t->iet >= t->ixt)
247 t->time = t->iet - t->ixt;
254 t->name = strdup(u.id);
255 if (t->name == NULL) {
266 free_unit_times(unit_times, (unsigned) c);
270 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
271 static struct boot_times times;
272 static bool cached = false;
277 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
279 if (bus_get_uint64_property(bus,
280 "/org/freedesktop/systemd1",
281 "org.freedesktop.systemd1.Manager",
282 "FirmwareTimestampMonotonic",
283 ×.firmware_time) < 0 ||
284 bus_get_uint64_property(bus,
285 "/org/freedesktop/systemd1",
286 "org.freedesktop.systemd1.Manager",
287 "LoaderTimestampMonotonic",
288 ×.loader_time) < 0 ||
289 bus_get_uint64_property(bus,
290 "/org/freedesktop/systemd1",
291 "org.freedesktop.systemd1.Manager",
293 ×.kernel_time) < 0 ||
294 bus_get_uint64_property(bus,
295 "/org/freedesktop/systemd1",
296 "org.freedesktop.systemd1.Manager",
297 "InitRDTimestampMonotonic",
298 ×.initrd_time) < 0 ||
299 bus_get_uint64_property(bus,
300 "/org/freedesktop/systemd1",
301 "org.freedesktop.systemd1.Manager",
302 "UserspaceTimestampMonotonic",
303 ×.userspace_time) < 0 ||
304 bus_get_uint64_property(bus,
305 "/org/freedesktop/systemd1",
306 "org.freedesktop.systemd1.Manager",
307 "FinishTimestampMonotonic",
308 ×.finish_time) < 0 ||
309 bus_get_uint64_property(bus,
310 "/org/freedesktop/systemd1",
311 "org.freedesktop.systemd1.Manager",
312 "GeneratorsStartTimestampMonotonic",
313 ×.generators_start_time) < 0 ||
314 bus_get_uint64_property(bus,
315 "/org/freedesktop/systemd1",
316 "org.freedesktop.systemd1.Manager",
317 "GeneratorsFinishTimestampMonotonic",
318 ×.generators_finish_time) < 0)
321 if (times.finish_time <= 0) {
322 log_error("Bootup is not yet finished. Please try again later.");
326 if (times.initrd_time)
327 times.kernel_done_time = times.initrd_time;
329 times.kernel_done_time = times.userspace_time;
338 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
339 char ts[FORMAT_TIMESPAN_MAX];
340 struct boot_times *t;
341 static char buf[4096];
346 r = acquire_boot_times(bus, &t);
353 size = strpcpyf(&ptr, size, "Startup finished in ");
354 if (t->firmware_time)
355 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
357 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
359 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
360 if (t->initrd_time > 0)
361 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
363 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
364 if (t->kernel_time > 0)
365 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
367 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
377 static void svg_graph_box(double height, double begin, double end) {
380 /* outside box, fill */
381 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
382 SCALE_X * (end - begin), SCALE_Y * height);
384 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
385 /* lines for each second */
386 if (i % 5000000 == 0)
387 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
388 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
389 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
390 else if (i % 1000000 == 0)
391 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
392 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
393 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
395 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
396 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
400 static int analyze_plot(DBusConnection *bus) {
401 struct unit_times *times;
402 struct boot_times *boot;
406 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
407 struct unit_times *u;
409 n = acquire_boot_times(bus, &boot);
413 n = pretty_boot_time(bus, &pretty_times);
417 get_os_name(&osname);
418 assert_se(uname(&name) >= 0);
420 n = acquire_time_data(bus, ×);
424 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
426 width = SCALE_X * (boot->firmware_time + boot->finish_time);
430 if (boot->firmware_time > boot->loader_time)
432 if (boot->loader_time) {
437 if (boot->initrd_time)
439 if (boot->kernel_time)
442 for (u = times; u < times + n; u++) {
445 if (u->ixt < boot->userspace_time ||
446 u->ixt > boot->finish_time) {
451 len = ((boot->firmware_time + u->ixt) * SCALE_X)
452 + (10.0 * strlen(u->name));
456 if (u->iet > u->ixt && u->iet <= boot->finish_time
457 && u->aet == 0 && u->axt == 0)
458 u->aet = u->axt = u->iet;
459 if (u->aet < u->ixt || u->aet > boot->finish_time)
460 u->aet = boot->finish_time;
461 if (u->axt < u->aet || u->aet > boot->finish_time)
462 u->axt = boot->finish_time;
463 if (u->iet < u->axt || u->iet > boot->finish_time)
464 u->iet = boot->finish_time;
468 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
469 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
470 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
472 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
473 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
474 80.0 + width, 150.0 + (m * SCALE_Y) +
475 4 * SCALE_Y /* legend */);
477 /* write some basic info as a comment, including some help */
478 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
479 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
480 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
481 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
482 "<!-- point your browser to this file. -->\n\n"
483 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
486 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
487 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
488 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
489 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
490 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
491 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
492 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
493 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
494 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
495 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
496 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
497 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
498 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
500 " line.sec5 { stroke-width: 2; }\n"
501 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
502 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
503 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
504 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
505 " text.sec { font-size: 8; }\n"
506 " ]]>\n </style>\n</defs>\n\n");
508 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
509 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
510 isempty(osname) ? "Linux" : osname,
511 name.nodename, name.release, name.version, name.machine);
513 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
514 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
516 if (boot->firmware_time) {
517 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
518 svg_text(true, -(double) boot->firmware_time, y, "firmware");
521 if (boot->loader_time) {
522 svg_bar("loader", -(double) boot->loader_time, 0, y);
523 svg_text(true, -(double) boot->loader_time, y, "loader");
526 if (boot->kernel_time) {
527 svg_bar("kernel", 0, boot->kernel_done_time, y);
528 svg_text(true, 0, y, "kernel");
531 if (boot->initrd_time) {
532 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
533 svg_text(true, boot->initrd_time, y, "initrd");
536 svg_bar("active", boot->userspace_time, boot->finish_time, y);
537 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
538 svg_text("left", boot->userspace_time, y, "systemd");
541 for (u = times; u < times + n; u++) {
542 char ts[FORMAT_TIMESPAN_MAX];
548 svg_bar("activating", u->ixt, u->aet, y);
549 svg_bar("active", u->aet, u->axt, y);
550 svg_bar("deactivating", u->axt, u->iet, y);
552 b = u->ixt * SCALE_X > width * 2 / 3;
554 svg_text(b, u->ixt, y, "%s (%s)",
555 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
557 svg_text(b, u->ixt, y, "%s", u->name);
563 svg_bar("activating", 0, 300000, y);
564 svg_text("right", 400000, y, "Activating");
566 svg_bar("active", 0, 300000, y);
567 svg_text("right", 400000, y, "Active");
569 svg_bar("deactivating", 0, 300000, y);
570 svg_text("right", 400000, y, "Deactivating");
572 svg_bar("generators", 0, 300000, y);
573 svg_text("right", 400000, y, "Generators");
581 free_unit_times(times, (unsigned) n);
587 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
588 bool last, struct unit_times *times, struct boot_times *boot) {
590 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
592 for (i = level; i != 0; i--)
593 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
595 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
599 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
600 format_timespan(ts, sizeof(ts), times->ixt - boot->userspace_time, USEC_PER_MSEC),
601 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
602 else if (times->aet > boot->userspace_time)
603 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
606 } else printf("%s", name);
612 static int list_dependencies_get_dependencies(DBusConnection *bus, const char *name, char ***deps) {
613 static const char dependencies[] =
616 _cleanup_free_ char *path;
617 const char *interface = "org.freedesktop.systemd1.Unit";
619 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
620 DBusMessageIter iter, sub, sub2, sub3;
629 path = unit_dbus_path_from_name(name);
635 r = bus_method_call_with_reply(
637 "org.freedesktop.systemd1",
639 "org.freedesktop.DBus.Properties",
643 DBUS_TYPE_STRING, &interface,
648 if (!dbus_message_iter_init(reply, &iter) ||
649 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
650 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
651 log_error("Failed to parse reply.");
656 dbus_message_iter_recurse(&iter, &sub);
658 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
661 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
662 dbus_message_iter_recurse(&sub, &sub2);
664 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
665 log_error("Failed to parse reply.");
670 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
671 log_error("Failed to parse reply.");
676 dbus_message_iter_recurse(&sub2, &sub3);
677 dbus_message_iter_next(&sub);
679 if (!nulstr_contains(dependencies, prop))
682 if (dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_ARRAY) {
683 if (dbus_message_iter_get_element_type(&sub3) == DBUS_TYPE_STRING) {
684 DBusMessageIter sub4;
685 dbus_message_iter_recurse(&sub3, &sub4);
687 while (dbus_message_iter_get_arg_type(&sub4) != DBUS_TYPE_INVALID) {
690 assert(dbus_message_iter_get_arg_type(&sub4) == DBUS_TYPE_STRING);
691 dbus_message_iter_get_basic(&sub4, &s);
693 r = strv_extend(&ret, s);
699 dbus_message_iter_next(&sub4);
712 static Hashmap *unit_times_hashmap;
714 static int list_dependencies_compare(const void *_a, const void *_b) {
715 const char **a = (const char**) _a, **b = (const char**) _b;
716 usec_t usa = 0, usb = 0;
717 struct unit_times *times;
719 times = hashmap_get(unit_times_hashmap, *a);
722 times = hashmap_get(unit_times_hashmap, *b);
729 static int list_dependencies_one(DBusConnection *bus, const char *name, unsigned int level, char ***units,
730 unsigned int branches) {
731 _cleanup_strv_free_ char **deps = NULL;
734 usec_t service_longest = 0;
736 struct unit_times *times;
737 struct boot_times *boot;
739 if(strv_extend(units, name))
742 r = list_dependencies_get_dependencies(bus, name, &deps);
746 qsort(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
748 r = acquire_boot_times(bus, &boot);
752 STRV_FOREACH(c, deps) {
753 times = hashmap_get(unit_times_hashmap, *c);
756 && times->aet <= boot->finish_time
757 && (times->aet >= service_longest
758 || service_longest == 0)) {
759 service_longest = times->aet;
764 if (service_longest == 0 )
767 STRV_FOREACH(c, deps) {
768 times = hashmap_get(unit_times_hashmap, *c);
769 if (times && times->aet
770 && times->aet <= boot->finish_time
771 && (service_longest - times->aet) <= arg_fuzz) {
779 STRV_FOREACH(c, deps) {
780 times = hashmap_get(unit_times_hashmap, *c);
783 || times->aet > boot->finish_time
784 || service_longest - times->aet > arg_fuzz)
789 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
793 if (strv_contains(*units, *c)) {
794 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
799 r = list_dependencies_one(bus, *c, level + 1, units,
800 (branches << 1) | (to_print ? 1 : 0));
812 static int list_dependencies(DBusConnection *bus) {
813 _cleanup_strv_free_ char **units = NULL;
814 char ts[FORMAT_TIMESPAN_MAX];
815 struct unit_times *times;
819 *interface = "org.freedesktop.systemd1.Unit",
821 DBusMessageIter iter, sub;
822 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
823 struct boot_times *boot;
827 path = unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET);
831 r = bus_method_call_with_reply (
833 "org.freedesktop.systemd1",
835 "org.freedesktop.DBus.Properties",
839 DBUS_TYPE_STRING, &interface,
840 DBUS_TYPE_STRING, &property,
845 if (!dbus_message_iter_init(reply, &iter) ||
846 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
847 log_error("Failed to parse reply.");
851 dbus_message_iter_recurse(&iter, &sub);
853 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
854 log_error("Failed to parse reply.");
858 dbus_message_iter_get_basic(&sub, &id);
860 times = hashmap_get(unit_times_hashmap, id);
862 r = acquire_boot_times(bus, &boot);
868 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
869 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
870 else if (times->aet > boot->userspace_time)
871 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
876 return list_dependencies_one(bus, SPECIAL_DEFAULT_TARGET, 0, &units, 0);
879 static int analyze_critical_chain(DBusConnection *bus) {
880 struct unit_times *times;
885 n = acquire_time_data(bus, ×);
889 h = hashmap_new(string_hash_func, string_compare_func);
893 for (i = 0; i < (unsigned)n; i++) {
894 r = hashmap_put(h, times[i].name, ×[i]);
898 unit_times_hashmap = h;
900 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
901 "The time the unit takes to start is printed after the \"+\" character.\n");
903 list_dependencies(bus);
906 free_unit_times(times, (unsigned) n);
910 static int analyze_blame(DBusConnection *bus) {
911 struct unit_times *times;
915 n = acquire_time_data(bus, ×);
919 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
921 for (i = 0; i < (unsigned) n; i++) {
922 char ts[FORMAT_TIMESPAN_MAX];
924 if (times[i].time > 0)
925 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
928 free_unit_times(times, (unsigned) n);
932 static int analyze_time(DBusConnection *bus) {
933 _cleanup_free_ char *buf = NULL;
936 r = pretty_boot_time(bus, &buf);
944 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
946 static const char * const colors[] = {
947 "Requires", "[color=\"black\"]",
948 "RequiresOverridable", "[color=\"black\"]",
949 "Requisite", "[color=\"darkblue\"]",
950 "RequisiteOverridable", "[color=\"darkblue\"]",
951 "Wants", "[color=\"grey66\"]",
952 "Conflicts", "[color=\"red\"]",
953 "ConflictedBy", "[color=\"red\"]",
954 "After", "[color=\"green\"]"
957 const char *c = NULL;
964 for (i = 0; i < ELEMENTSOF(colors); i += 2)
965 if (streq(colors[i], prop)) {
973 if (arg_dot != DEP_ALL)
974 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
977 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
978 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
981 dbus_message_iter_recurse(iter, &sub);
983 for (dbus_message_iter_recurse(iter, &sub);
984 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
985 dbus_message_iter_next(&sub)) {
990 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
991 dbus_message_iter_get_basic(&sub, &s);
993 if (!strv_isempty(arg_dot_from_patterns)) {
996 STRV_FOREACH(p, arg_dot_from_patterns)
997 if (fnmatch(*p, name, 0) == 0) {
1006 if (!strv_isempty(arg_dot_to_patterns)) {
1007 match_found = false;
1009 STRV_FOREACH(p, arg_dot_to_patterns)
1010 if (fnmatch(*p, s, 0) == 0) {
1019 if (!strv_isempty(patterns)) {
1020 match_found = false;
1022 STRV_FOREACH(p, patterns)
1023 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
1031 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
1038 static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
1039 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1040 const char *interface = "org.freedesktop.systemd1.Unit";
1042 DBusMessageIter iter, sub, sub2, sub3;
1047 r = bus_method_call_with_reply(
1049 "org.freedesktop.systemd1",
1051 "org.freedesktop.DBus.Properties",
1055 DBUS_TYPE_STRING, &interface,
1060 if (!dbus_message_iter_init(reply, &iter) ||
1061 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1062 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
1063 log_error("Failed to parse reply.");
1067 for (dbus_message_iter_recurse(&iter, &sub);
1068 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1069 dbus_message_iter_next(&sub)) {
1072 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
1073 dbus_message_iter_recurse(&sub, &sub2);
1075 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
1076 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
1077 log_error("Failed to parse reply.");
1081 dbus_message_iter_recurse(&sub2, &sub3);
1082 r = graph_one_property(u->id, prop, &sub3, patterns);
1090 static int dot(DBusConnection *bus, char* patterns[]) {
1091 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1092 DBusMessageIter iter, sub;
1095 r = bus_method_call_with_reply(
1097 "org.freedesktop.systemd1",
1098 "/org/freedesktop/systemd1",
1099 "org.freedesktop.systemd1.Manager",
1107 if (!dbus_message_iter_init(reply, &iter) ||
1108 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1109 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
1110 log_error("Failed to parse reply.");
1114 printf("digraph systemd {\n");
1116 for (dbus_message_iter_recurse(&iter, &sub);
1117 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1118 dbus_message_iter_next(&sub)) {
1121 r = bus_parse_unit_info(&sub, &u);
1125 r = graph_one(bus, &u, patterns);
1132 log_info(" Color legend: black = Requires\n"
1133 " dark blue = Requisite\n"
1134 " dark grey = Wants\n"
1135 " red = Conflicts\n"
1136 " green = After\n");
1139 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1140 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1145 static void analyze_help(void)
1147 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1148 "Process systemd profiling information\n\n"
1149 " -h --help Show this help\n"
1150 " --version Show package version\n"
1151 " --system Connect to system manager\n"
1152 " --user Connect to user service manager\n"
1153 " --order When generating a dependency graph, show only order\n"
1154 " --require When generating a dependency graph, show only requirement\n"
1155 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1156 " When generating a dependency graph, filter only origins\n"
1157 " or destinations, respectively\n"
1158 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1159 " services, which finished TIMESPAN earlier, than the\n"
1160 " latest in the branch. The unit of TIMESPAN is seconds\n"
1161 " unless specified with a different unit, i.e. 50ms\n\n"
1163 " time Print time spent in the kernel before reaching userspace\n"
1164 " blame Print list of running units ordered by time to init\n"
1165 " critical-chain Print a tree of the time critical chain of units\n"
1166 " plot Output SVG graphic showing service initialization\n"
1167 " dot Dump dependency graph (in dot(1) format)\n\n",
1168 program_invocation_short_name);
1170 /* When updating this list, including descriptions, apply
1171 * changes to shell-completion/bash/systemd and
1172 * shell-completion/systemd-zsh-completion.zsh too. */
1175 static int parse_argv(int argc, char *argv[])
1180 ARG_VERSION = 0x100,
1185 ARG_DOT_FROM_PATTERN,
1190 static const struct option options[] = {
1191 { "help", no_argument, NULL, 'h' },
1192 { "version", no_argument, NULL, ARG_VERSION },
1193 { "order", no_argument, NULL, ARG_ORDER },
1194 { "require", no_argument, NULL, ARG_REQUIRE },
1195 { "user", no_argument, NULL, ARG_USER },
1196 { "system", no_argument, NULL, ARG_SYSTEM },
1197 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
1198 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1199 { "fuzz", required_argument, NULL, ARG_FUZZ },
1200 { NULL, 0, NULL, 0 }
1207 switch (getopt_long(argc, argv, "h", options, NULL)) {
1214 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1218 arg_scope = UNIT_FILE_USER;
1222 arg_scope = UNIT_FILE_SYSTEM;
1226 arg_dot = DEP_ORDER;
1230 arg_dot = DEP_REQUIRE;
1233 case ARG_DOT_FROM_PATTERN:
1234 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1239 case ARG_DOT_TO_PATTERN:
1240 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1246 r = parse_sec(optarg, &arg_fuzz);
1258 assert_not_reached("Unhandled option");
1263 int main(int argc, char *argv[]) {
1265 DBusConnection *bus = NULL;
1267 setlocale(LC_ALL, "");
1268 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1269 log_parse_environment();
1272 r = parse_argv(argc, argv);
1274 return EXIT_FAILURE;
1276 return EXIT_SUCCESS;
1278 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
1280 return EXIT_FAILURE;
1282 if (!argv[optind] || streq(argv[optind], "time"))
1283 r = analyze_time(bus);
1284 else if (streq(argv[optind], "blame"))
1285 r = analyze_blame(bus);
1286 else if (streq(argv[optind], "critical-chain"))
1287 r = analyze_critical_chain(bus);
1288 else if (streq(argv[optind], "plot"))
1289 r = analyze_plot(bus);
1290 else if (streq(argv[optind], "dot"))
1291 r = dot(bus, argv+optind+1);
1293 log_error("Unknown operation '%s'.", argv[optind]);
1295 strv_free(arg_dot_from_patterns);
1296 strv_free(arg_dot_to_patterns);
1297 dbus_connection_unref(bus);
1299 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;