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;
83 usec_t unitsload_start_time;
84 usec_t unitsload_finish_time;
95 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
96 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
97 DBusMessageIter iter, sub;
100 r = bus_method_call_with_reply(
102 "org.freedesktop.systemd1",
104 "org.freedesktop.DBus.Properties",
108 DBUS_TYPE_STRING, &interface,
109 DBUS_TYPE_STRING, &property,
114 if (!dbus_message_iter_init(reply, &iter) ||
115 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
116 log_error("Failed to parse reply.");
120 dbus_message_iter_recurse(&iter, &sub);
122 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
123 log_error("Failed to parse reply.");
127 dbus_message_iter_get_basic(&sub, val);
132 static int compare_unit_time(const void *a, const void *b) {
133 return compare(((struct unit_times *)b)->time,
134 ((struct unit_times *)a)->time);
137 static int compare_unit_start(const void *a, const void *b) {
138 return compare(((struct unit_times *)a)->ixt,
139 ((struct unit_times *)b)->ixt);
142 static int get_os_name(char **_n) {
146 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
157 static void free_unit_times(struct unit_times *t, unsigned n) {
158 struct unit_times *p;
160 for (p = t; p < t + n; p++)
166 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
167 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
168 DBusMessageIter iter, sub;
169 int r, c = 0, n_units = 0;
170 struct unit_times *unit_times = NULL;
172 r = bus_method_call_with_reply(
174 "org.freedesktop.systemd1",
175 "/org/freedesktop/systemd1",
176 "org.freedesktop.systemd1.Manager",
184 if (!dbus_message_iter_init(reply, &iter) ||
185 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
186 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
187 log_error("Failed to parse reply.");
192 for (dbus_message_iter_recurse(&iter, &sub);
193 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
194 dbus_message_iter_next(&sub)) {
196 struct unit_times *t;
198 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
199 log_error("Failed to parse reply.");
205 struct unit_times *w;
207 n_units = MAX(2*c, 16);
208 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
220 r = bus_parse_unit_info(&sub, &u);
224 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
226 if (bus_get_uint64_property(bus, u.unit_path,
227 "org.freedesktop.systemd1.Unit",
228 "InactiveExitTimestampMonotonic",
230 bus_get_uint64_property(bus, u.unit_path,
231 "org.freedesktop.systemd1.Unit",
232 "ActiveEnterTimestampMonotonic",
234 bus_get_uint64_property(bus, u.unit_path,
235 "org.freedesktop.systemd1.Unit",
236 "ActiveExitTimestampMonotonic",
238 bus_get_uint64_property(bus, u.unit_path,
239 "org.freedesktop.systemd1.Unit",
240 "InactiveEnterTimestampMonotonic",
246 if (t->aet >= t->ixt)
247 t->time = t->aet - t->ixt;
248 else if (t->iet >= t->ixt)
249 t->time = t->iet - t->ixt;
256 t->name = strdup(u.id);
257 if (t->name == NULL) {
268 free_unit_times(unit_times, (unsigned) c);
272 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
273 static struct boot_times times;
274 static bool cached = false;
279 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
281 if (bus_get_uint64_property(bus,
282 "/org/freedesktop/systemd1",
283 "org.freedesktop.systemd1.Manager",
284 "FirmwareTimestampMonotonic",
285 ×.firmware_time) < 0 ||
286 bus_get_uint64_property(bus,
287 "/org/freedesktop/systemd1",
288 "org.freedesktop.systemd1.Manager",
289 "LoaderTimestampMonotonic",
290 ×.loader_time) < 0 ||
291 bus_get_uint64_property(bus,
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
295 ×.kernel_time) < 0 ||
296 bus_get_uint64_property(bus,
297 "/org/freedesktop/systemd1",
298 "org.freedesktop.systemd1.Manager",
299 "InitRDTimestampMonotonic",
300 ×.initrd_time) < 0 ||
301 bus_get_uint64_property(bus,
302 "/org/freedesktop/systemd1",
303 "org.freedesktop.systemd1.Manager",
304 "UserspaceTimestampMonotonic",
305 ×.userspace_time) < 0 ||
306 bus_get_uint64_property(bus,
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
309 "FinishTimestampMonotonic",
310 ×.finish_time) < 0 ||
311 bus_get_uint64_property(bus,
312 "/org/freedesktop/systemd1",
313 "org.freedesktop.systemd1.Manager",
314 "GeneratorsStartTimestampMonotonic",
315 ×.generators_start_time) < 0 ||
316 bus_get_uint64_property(bus,
317 "/org/freedesktop/systemd1",
318 "org.freedesktop.systemd1.Manager",
319 "GeneratorsFinishTimestampMonotonic",
320 ×.generators_finish_time) < 0 ||
321 bus_get_uint64_property(bus,
322 "/org/freedesktop/systemd1",
323 "org.freedesktop.systemd1.Manager",
324 "UnitsLoadStartTimestampMonotonic",
325 ×.unitsload_start_time) < 0 ||
326 bus_get_uint64_property(bus,
327 "/org/freedesktop/systemd1",
328 "org.freedesktop.systemd1.Manager",
329 "UnitsLoadFinishTimestampMonotonic",
330 ×.unitsload_finish_time) < 0)
333 if (times.finish_time <= 0) {
334 log_error("Bootup is not yet finished. Please try again later.");
338 if (times.initrd_time)
339 times.kernel_done_time = times.initrd_time;
341 times.kernel_done_time = times.userspace_time;
350 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
351 char ts[FORMAT_TIMESPAN_MAX];
352 struct boot_times *t;
353 static char buf[4096];
358 r = acquire_boot_times(bus, &t);
365 size = strpcpyf(&ptr, size, "Startup finished in ");
366 if (t->firmware_time)
367 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
369 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
371 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
372 if (t->initrd_time > 0)
373 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
375 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
376 if (t->kernel_time > 0)
377 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
379 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
389 static void svg_graph_box(double height, double begin, double end) {
392 /* outside box, fill */
393 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
394 SCALE_X * (end - begin), SCALE_Y * height);
396 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
397 /* lines for each second */
398 if (i % 5000000 == 0)
399 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
400 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
401 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
402 else if (i % 1000000 == 0)
403 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
404 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
405 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
407 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
408 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
412 static int analyze_plot(DBusConnection *bus) {
413 struct unit_times *times;
414 struct boot_times *boot;
418 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
419 struct unit_times *u;
421 n = acquire_boot_times(bus, &boot);
425 n = pretty_boot_time(bus, &pretty_times);
429 get_os_name(&osname);
430 assert_se(uname(&name) >= 0);
432 n = acquire_time_data(bus, ×);
436 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
438 width = SCALE_X * (boot->firmware_time + boot->finish_time);
442 if (boot->firmware_time > boot->loader_time)
444 if (boot->loader_time) {
449 if (boot->initrd_time)
451 if (boot->kernel_time)
454 for (u = times; u < times + n; u++) {
457 if (u->ixt < boot->userspace_time ||
458 u->ixt > boot->finish_time) {
463 len = ((boot->firmware_time + u->ixt) * SCALE_X)
464 + (10.0 * strlen(u->name));
468 if (u->iet > u->ixt && u->iet <= boot->finish_time
469 && u->aet == 0 && u->axt == 0)
470 u->aet = u->axt = u->iet;
471 if (u->aet < u->ixt || u->aet > boot->finish_time)
472 u->aet = boot->finish_time;
473 if (u->axt < u->aet || u->aet > boot->finish_time)
474 u->axt = boot->finish_time;
475 if (u->iet < u->axt || u->iet > boot->finish_time)
476 u->iet = boot->finish_time;
480 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
481 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
482 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
484 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
485 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
486 80.0 + width, 150.0 + (m * SCALE_Y) +
487 5 * SCALE_Y /* legend */);
489 /* write some basic info as a comment, including some help */
490 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
491 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
492 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
493 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
494 "<!-- point your browser to this file. -->\n\n"
495 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
498 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
499 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
500 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
501 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
502 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
503 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
504 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
505 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
506 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
507 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
508 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
509 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
510 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
511 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
513 " line.sec5 { stroke-width: 2; }\n"
514 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
515 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
516 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
517 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
518 " text.sec { font-size: 10px; }\n"
519 " ]]>\n </style>\n</defs>\n\n");
521 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
522 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
523 isempty(osname) ? "Linux" : osname,
524 name.nodename, name.release, name.version, name.machine);
526 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
527 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
529 if (boot->firmware_time) {
530 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
531 svg_text(true, -(double) boot->firmware_time, y, "firmware");
534 if (boot->loader_time) {
535 svg_bar("loader", -(double) boot->loader_time, 0, y);
536 svg_text(true, -(double) boot->loader_time, y, "loader");
539 if (boot->kernel_time) {
540 svg_bar("kernel", 0, boot->kernel_done_time, y);
541 svg_text(true, 0, y, "kernel");
544 if (boot->initrd_time) {
545 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
546 svg_text(true, boot->initrd_time, y, "initrd");
549 svg_bar("active", boot->userspace_time, boot->finish_time, y);
550 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
551 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
552 svg_text("left", boot->userspace_time, y, "systemd");
555 for (u = times; u < times + n; u++) {
556 char ts[FORMAT_TIMESPAN_MAX];
562 svg_bar("activating", u->ixt, u->aet, y);
563 svg_bar("active", u->aet, u->axt, y);
564 svg_bar("deactivating", u->axt, u->iet, y);
566 b = u->ixt * SCALE_X > width * 2 / 3;
568 svg_text(b, u->ixt, y, "%s (%s)",
569 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
571 svg_text(b, u->ixt, y, "%s", u->name);
577 svg_bar("activating", 0, 300000, y);
578 svg_text("right", 400000, y, "Activating");
580 svg_bar("active", 0, 300000, y);
581 svg_text("right", 400000, y, "Active");
583 svg_bar("deactivating", 0, 300000, y);
584 svg_text("right", 400000, y, "Deactivating");
586 svg_bar("generators", 0, 300000, y);
587 svg_text("right", 400000, y, "Generators");
589 svg_bar("unitsload", 0, 300000, y);
590 svg_text("right", 400000, y, "Loading unit files");
598 free_unit_times(times, (unsigned) n);
604 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
605 bool last, struct unit_times *times, struct boot_times *boot) {
607 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
609 for (i = level; i != 0; i--)
610 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
612 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
616 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
617 format_timespan(ts, sizeof(ts), times->ixt - boot->userspace_time, USEC_PER_MSEC),
618 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
619 else if (times->aet > boot->userspace_time)
620 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
623 } else printf("%s", name);
629 static int list_dependencies_get_dependencies(DBusConnection *bus, const char *name, char ***deps) {
630 static const char dependencies[] =
633 _cleanup_free_ char *path;
634 const char *interface = "org.freedesktop.systemd1.Unit";
636 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
637 DBusMessageIter iter, sub, sub2, sub3;
646 path = unit_dbus_path_from_name(name);
652 r = bus_method_call_with_reply(
654 "org.freedesktop.systemd1",
656 "org.freedesktop.DBus.Properties",
660 DBUS_TYPE_STRING, &interface,
665 if (!dbus_message_iter_init(reply, &iter) ||
666 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
667 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
668 log_error("Failed to parse reply.");
673 dbus_message_iter_recurse(&iter, &sub);
675 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
678 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
679 dbus_message_iter_recurse(&sub, &sub2);
681 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
682 log_error("Failed to parse reply.");
687 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
688 log_error("Failed to parse reply.");
693 dbus_message_iter_recurse(&sub2, &sub3);
694 dbus_message_iter_next(&sub);
696 if (!nulstr_contains(dependencies, prop))
699 if (dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_ARRAY) {
700 if (dbus_message_iter_get_element_type(&sub3) == DBUS_TYPE_STRING) {
701 DBusMessageIter sub4;
702 dbus_message_iter_recurse(&sub3, &sub4);
704 while (dbus_message_iter_get_arg_type(&sub4) != DBUS_TYPE_INVALID) {
707 assert(dbus_message_iter_get_arg_type(&sub4) == DBUS_TYPE_STRING);
708 dbus_message_iter_get_basic(&sub4, &s);
710 r = strv_extend(&ret, s);
716 dbus_message_iter_next(&sub4);
729 static Hashmap *unit_times_hashmap;
731 static int list_dependencies_compare(const void *_a, const void *_b) {
732 const char **a = (const char**) _a, **b = (const char**) _b;
733 usec_t usa = 0, usb = 0;
734 struct unit_times *times;
736 times = hashmap_get(unit_times_hashmap, *a);
739 times = hashmap_get(unit_times_hashmap, *b);
746 static int list_dependencies_one(DBusConnection *bus, const char *name, unsigned int level, char ***units,
747 unsigned int branches) {
748 _cleanup_strv_free_ char **deps = NULL;
751 usec_t service_longest = 0;
753 struct unit_times *times;
754 struct boot_times *boot;
756 if(strv_extend(units, name))
759 r = list_dependencies_get_dependencies(bus, name, &deps);
763 qsort(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
765 r = acquire_boot_times(bus, &boot);
769 STRV_FOREACH(c, deps) {
770 times = hashmap_get(unit_times_hashmap, *c);
773 && times->aet <= boot->finish_time
774 && (times->aet >= service_longest
775 || service_longest == 0)) {
776 service_longest = times->aet;
781 if (service_longest == 0 )
784 STRV_FOREACH(c, deps) {
785 times = hashmap_get(unit_times_hashmap, *c);
786 if (times && times->aet
787 && times->aet <= boot->finish_time
788 && (service_longest - times->aet) <= arg_fuzz) {
796 STRV_FOREACH(c, deps) {
797 times = hashmap_get(unit_times_hashmap, *c);
800 || times->aet > boot->finish_time
801 || service_longest - times->aet > arg_fuzz)
806 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
810 if (strv_contains(*units, *c)) {
811 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
816 r = list_dependencies_one(bus, *c, level + 1, units,
817 (branches << 1) | (to_print ? 1 : 0));
829 static int list_dependencies(DBusConnection *bus, const char *name) {
830 _cleanup_strv_free_ char **units = NULL;
831 char ts[FORMAT_TIMESPAN_MAX];
832 struct unit_times *times;
836 *interface = "org.freedesktop.systemd1.Unit",
838 DBusMessageIter iter, sub;
839 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
840 struct boot_times *boot;
844 path = unit_dbus_path_from_name(name);
848 r = bus_method_call_with_reply (
850 "org.freedesktop.systemd1",
852 "org.freedesktop.DBus.Properties",
856 DBUS_TYPE_STRING, &interface,
857 DBUS_TYPE_STRING, &property,
862 if (!dbus_message_iter_init(reply, &iter) ||
863 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
864 log_error("Failed to parse reply.");
868 dbus_message_iter_recurse(&iter, &sub);
870 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
871 log_error("Failed to parse reply.");
875 dbus_message_iter_get_basic(&sub, &id);
877 times = hashmap_get(unit_times_hashmap, id);
879 r = acquire_boot_times(bus, &boot);
885 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
886 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
887 else if (times->aet > boot->userspace_time)
888 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
893 return list_dependencies_one(bus, name, 0, &units, 0);
896 static int analyze_critical_chain(DBusConnection *bus, char *names[]) {
897 struct unit_times *times;
902 n = acquire_time_data(bus, ×);
906 h = hashmap_new(string_hash_func, string_compare_func);
910 for (i = 0; i < (unsigned)n; i++) {
911 r = hashmap_put(h, times[i].name, ×[i]);
915 unit_times_hashmap = h;
917 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
918 "The time the unit takes to start is printed after the \"+\" character.\n");
920 if (!strv_isempty(names)) {
922 STRV_FOREACH(name, names)
923 list_dependencies(bus, *name);
925 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
929 free_unit_times(times, (unsigned) n);
933 static int analyze_blame(DBusConnection *bus) {
934 struct unit_times *times;
938 n = acquire_time_data(bus, ×);
942 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
944 for (i = 0; i < (unsigned) n; i++) {
945 char ts[FORMAT_TIMESPAN_MAX];
947 if (times[i].time > 0)
948 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
951 free_unit_times(times, (unsigned) n);
955 static int analyze_time(DBusConnection *bus) {
956 _cleanup_free_ char *buf = NULL;
959 r = pretty_boot_time(bus, &buf);
967 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
969 static const char * const colors[] = {
970 "Requires", "[color=\"black\"]",
971 "RequiresOverridable", "[color=\"black\"]",
972 "Requisite", "[color=\"darkblue\"]",
973 "RequisiteOverridable", "[color=\"darkblue\"]",
974 "Wants", "[color=\"grey66\"]",
975 "Conflicts", "[color=\"red\"]",
976 "ConflictedBy", "[color=\"red\"]",
977 "After", "[color=\"green\"]"
980 const char *c = NULL;
987 for (i = 0; i < ELEMENTSOF(colors); i += 2)
988 if (streq(colors[i], prop)) {
996 if (arg_dot != DEP_ALL)
997 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
1000 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
1001 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
1002 DBusMessageIter sub;
1004 dbus_message_iter_recurse(iter, &sub);
1006 for (dbus_message_iter_recurse(iter, &sub);
1007 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1008 dbus_message_iter_next(&sub)) {
1013 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
1014 dbus_message_iter_get_basic(&sub, &s);
1016 if (!strv_isempty(arg_dot_from_patterns)) {
1017 match_found = false;
1019 STRV_FOREACH(p, arg_dot_from_patterns)
1020 if (fnmatch(*p, name, 0) == 0) {
1029 if (!strv_isempty(arg_dot_to_patterns)) {
1030 match_found = false;
1032 STRV_FOREACH(p, arg_dot_to_patterns)
1033 if (fnmatch(*p, s, 0) == 0) {
1042 if (!strv_isempty(patterns)) {
1043 match_found = false;
1045 STRV_FOREACH(p, patterns)
1046 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
1054 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
1061 static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
1062 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1063 const char *interface = "org.freedesktop.systemd1.Unit";
1065 DBusMessageIter iter, sub, sub2, sub3;
1070 r = bus_method_call_with_reply(
1072 "org.freedesktop.systemd1",
1074 "org.freedesktop.DBus.Properties",
1078 DBUS_TYPE_STRING, &interface,
1083 if (!dbus_message_iter_init(reply, &iter) ||
1084 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1085 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
1086 log_error("Failed to parse reply.");
1090 for (dbus_message_iter_recurse(&iter, &sub);
1091 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1092 dbus_message_iter_next(&sub)) {
1095 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
1096 dbus_message_iter_recurse(&sub, &sub2);
1098 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
1099 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
1100 log_error("Failed to parse reply.");
1104 dbus_message_iter_recurse(&sub2, &sub3);
1105 r = graph_one_property(u->id, prop, &sub3, patterns);
1113 static int dot(DBusConnection *bus, char* patterns[]) {
1114 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1115 DBusMessageIter iter, sub;
1118 r = bus_method_call_with_reply(
1120 "org.freedesktop.systemd1",
1121 "/org/freedesktop/systemd1",
1122 "org.freedesktop.systemd1.Manager",
1130 if (!dbus_message_iter_init(reply, &iter) ||
1131 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1132 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
1133 log_error("Failed to parse reply.");
1137 printf("digraph systemd {\n");
1139 for (dbus_message_iter_recurse(&iter, &sub);
1140 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1141 dbus_message_iter_next(&sub)) {
1144 r = bus_parse_unit_info(&sub, &u);
1148 r = graph_one(bus, &u, patterns);
1155 log_info(" Color legend: black = Requires\n"
1156 " dark blue = Requisite\n"
1157 " dark grey = Wants\n"
1158 " red = Conflicts\n"
1159 " green = After\n");
1162 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1163 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1168 static void analyze_help(void)
1170 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1171 "Process systemd profiling information\n\n"
1172 " -h --help Show this help\n"
1173 " --version Show package version\n"
1174 " --system Connect to system manager\n"
1175 " --user Connect to user service manager\n"
1176 " --order When generating a dependency graph, show only order\n"
1177 " --require When generating a dependency graph, show only requirement\n"
1178 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1179 " When generating a dependency graph, filter only origins\n"
1180 " or destinations, respectively\n"
1181 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1182 " services, which finished TIMESPAN earlier, than the\n"
1183 " latest in the branch. The unit of TIMESPAN is seconds\n"
1184 " unless specified with a different unit, i.e. 50ms\n\n"
1186 " time Print time spent in the kernel before reaching userspace\n"
1187 " blame Print list of running units ordered by time to init\n"
1188 " critical-chain Print a tree of the time critical chain of units\n"
1189 " plot Output SVG graphic showing service initialization\n"
1190 " dot Dump dependency graph (in dot(1) format)\n\n",
1191 program_invocation_short_name);
1193 /* When updating this list, including descriptions, apply
1194 * changes to shell-completion/bash/systemd and
1195 * shell-completion/systemd-zsh-completion.zsh too. */
1198 static int parse_argv(int argc, char *argv[])
1203 ARG_VERSION = 0x100,
1208 ARG_DOT_FROM_PATTERN,
1213 static const struct option options[] = {
1214 { "help", no_argument, NULL, 'h' },
1215 { "version", no_argument, NULL, ARG_VERSION },
1216 { "order", no_argument, NULL, ARG_ORDER },
1217 { "require", no_argument, NULL, ARG_REQUIRE },
1218 { "user", no_argument, NULL, ARG_USER },
1219 { "system", no_argument, NULL, ARG_SYSTEM },
1220 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
1221 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1222 { "fuzz", required_argument, NULL, ARG_FUZZ },
1223 { NULL, 0, NULL, 0 }
1230 switch (getopt_long(argc, argv, "h", options, NULL)) {
1237 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1241 arg_scope = UNIT_FILE_USER;
1245 arg_scope = UNIT_FILE_SYSTEM;
1249 arg_dot = DEP_ORDER;
1253 arg_dot = DEP_REQUIRE;
1256 case ARG_DOT_FROM_PATTERN:
1257 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1262 case ARG_DOT_TO_PATTERN:
1263 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1269 r = parse_sec(optarg, &arg_fuzz);
1281 assert_not_reached("Unhandled option");
1286 int main(int argc, char *argv[]) {
1288 DBusConnection *bus = NULL;
1290 setlocale(LC_ALL, "");
1291 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1292 log_parse_environment();
1295 r = parse_argv(argc, argv);
1297 return EXIT_FAILURE;
1299 return EXIT_SUCCESS;
1301 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
1303 return EXIT_FAILURE;
1305 if (!argv[optind] || streq(argv[optind], "time"))
1306 r = analyze_time(bus);
1307 else if (streq(argv[optind], "blame"))
1308 r = analyze_blame(bus);
1309 else if (streq(argv[optind], "critical-chain"))
1310 r = analyze_critical_chain(bus, argv+optind+1);
1311 else if (streq(argv[optind], "plot"))
1312 r = analyze_plot(bus);
1313 else if (streq(argv[optind], "dot"))
1314 r = dot(bus, argv+optind+1);
1316 log_error("Unknown operation '%s'.", argv[optind]);
1318 strv_free(arg_dot_from_patterns);
1319 strv_free(arg_dot_to_patterns);
1320 dbus_connection_unref(bus);
1322 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;