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 "bus-error.h"
40 #include "unit-name.h"
45 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
48 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
50 #define svg(...) printf(__VA_ARGS__)
52 #define svg_bar(class, x1, x2, y) \
53 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
55 SCALE_X * (x1), SCALE_Y * (y), \
56 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
58 #define svg_text(b, x, y, format, ...) \
60 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
61 svg(format, ## __VA_ARGS__); \
70 static char** arg_dot_from_patterns = NULL;
71 static char** arg_dot_to_patterns = NULL;
72 static usec_t arg_fuzz = 0;
73 static bool arg_no_pager = false;
74 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
75 static char *arg_host = NULL;
76 static bool arg_user = false;
82 usec_t kernel_done_time;
84 usec_t userspace_time;
86 usec_t generators_start_time;
87 usec_t generators_finish_time;
88 usec_t unitsload_start_time;
89 usec_t unitsload_finish_time;
94 const char *description;
95 const char *load_state;
96 const char *active_state;
97 const char *sub_state;
98 const char *following;
99 const char *unit_path;
101 const char *job_type;
102 const char *job_path;
114 static void pager_open_if_enabled(void) {
122 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
123 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
126 r = sd_bus_get_property_trivial(
128 "org.freedesktop.systemd1",
136 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
143 static int compare_unit_time(const void *a, const void *b) {
144 return compare(((struct unit_times *)b)->time,
145 ((struct unit_times *)a)->time);
148 static int compare_unit_start(const void *a, const void *b) {
149 return compare(((struct unit_times *)a)->activating,
150 ((struct unit_times *)b)->activating);
153 static int get_os_name(char **_n) {
157 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
168 static void free_unit_times(struct unit_times *t, unsigned n) {
169 struct unit_times *p;
171 for (p = t; p < t + n; p++)
177 static int bus_parse_unit_info(sd_bus_message *message, struct unit_info *u) {
183 r = sd_bus_message_read(message, "(ssssssouso)", &u->id,
194 log_error("Failed to parse message as unit_info.");
201 static int bus_get_unit_property_strv(sd_bus *bus, const char *unit_path, const char *prop, char ***strv) {
202 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
203 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
207 r = sd_bus_get_property(
209 "org.freedesktop.systemd1",
211 "org.freedesktop.systemd1.Unit",
217 log_error("Failed to get unit property: %s %s", prop, bus_error_message(&error, -r));
221 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s");
225 while ((r = sd_bus_message_read(reply, "s", &s)) > 0) {
226 r = strv_extend(strv, s);
236 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
237 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
238 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
239 int r, c = 0, n_units = 0;
240 struct unit_times *unit_times = NULL;
243 r = sd_bus_call_method(
245 "org.freedesktop.systemd1",
246 "/org/freedesktop/systemd1",
247 "org.freedesktop.systemd1.Manager",
253 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
257 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
261 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
262 struct unit_times *t;
268 struct unit_times *w;
270 n_units = MAX(2*c, 16);
271 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
283 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
285 if (bus_get_uint64_property(bus, u.unit_path,
286 "org.freedesktop.systemd1.Unit",
287 "InactiveExitTimestampMonotonic",
288 &t->activating) < 0 ||
289 bus_get_uint64_property(bus, u.unit_path,
290 "org.freedesktop.systemd1.Unit",
291 "ActiveEnterTimestampMonotonic",
292 &t->activated) < 0 ||
293 bus_get_uint64_property(bus, u.unit_path,
294 "org.freedesktop.systemd1.Unit",
295 "ActiveExitTimestampMonotonic",
296 &t->deactivating) < 0 ||
297 bus_get_uint64_property(bus, u.unit_path,
298 "org.freedesktop.systemd1.Unit",
299 "InactiveEnterTimestampMonotonic",
300 &t->deactivated) < 0) {
305 if (t->activated >= t->activating)
306 t->time = t->activated - t->activating;
307 else if (t->deactivated >= t->activating)
308 t->time = t->deactivated - t->activating;
312 if (t->activating == 0)
315 t->name = strdup(u.id);
316 if (t->name == NULL) {
327 free_unit_times(unit_times, (unsigned) c);
331 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
332 static struct boot_times times;
333 static bool cached = false;
338 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
340 if (bus_get_uint64_property(bus,
341 "/org/freedesktop/systemd1",
342 "org.freedesktop.systemd1.Manager",
343 "FirmwareTimestampMonotonic",
344 ×.firmware_time) < 0 ||
345 bus_get_uint64_property(bus,
346 "/org/freedesktop/systemd1",
347 "org.freedesktop.systemd1.Manager",
348 "LoaderTimestampMonotonic",
349 ×.loader_time) < 0 ||
350 bus_get_uint64_property(bus,
351 "/org/freedesktop/systemd1",
352 "org.freedesktop.systemd1.Manager",
354 ×.kernel_time) < 0 ||
355 bus_get_uint64_property(bus,
356 "/org/freedesktop/systemd1",
357 "org.freedesktop.systemd1.Manager",
358 "InitRDTimestampMonotonic",
359 ×.initrd_time) < 0 ||
360 bus_get_uint64_property(bus,
361 "/org/freedesktop/systemd1",
362 "org.freedesktop.systemd1.Manager",
363 "UserspaceTimestampMonotonic",
364 ×.userspace_time) < 0 ||
365 bus_get_uint64_property(bus,
366 "/org/freedesktop/systemd1",
367 "org.freedesktop.systemd1.Manager",
368 "FinishTimestampMonotonic",
369 ×.finish_time) < 0 ||
370 bus_get_uint64_property(bus,
371 "/org/freedesktop/systemd1",
372 "org.freedesktop.systemd1.Manager",
373 "GeneratorsStartTimestampMonotonic",
374 ×.generators_start_time) < 0 ||
375 bus_get_uint64_property(bus,
376 "/org/freedesktop/systemd1",
377 "org.freedesktop.systemd1.Manager",
378 "GeneratorsFinishTimestampMonotonic",
379 ×.generators_finish_time) < 0 ||
380 bus_get_uint64_property(bus,
381 "/org/freedesktop/systemd1",
382 "org.freedesktop.systemd1.Manager",
383 "UnitsLoadStartTimestampMonotonic",
384 ×.unitsload_start_time) < 0 ||
385 bus_get_uint64_property(bus,
386 "/org/freedesktop/systemd1",
387 "org.freedesktop.systemd1.Manager",
388 "UnitsLoadFinishTimestampMonotonic",
389 ×.unitsload_finish_time) < 0)
392 if (times.finish_time <= 0) {
393 log_error("Bootup is not yet finished. Please try again later.");
397 if (times.initrd_time)
398 times.kernel_done_time = times.initrd_time;
400 times.kernel_done_time = times.userspace_time;
409 static int pretty_boot_time(sd_bus *bus, char **_buf) {
410 char ts[FORMAT_TIMESPAN_MAX];
411 struct boot_times *t;
412 static char buf[4096];
417 r = acquire_boot_times(bus, &t);
424 size = strpcpyf(&ptr, size, "Startup finished in ");
425 if (t->firmware_time)
426 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
428 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
430 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
431 if (t->initrd_time > 0)
432 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
434 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
435 if (t->kernel_time > 0)
436 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
438 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
448 static void svg_graph_box(double height, double begin, double end) {
451 /* outside box, fill */
452 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
453 SCALE_X * (end - begin), SCALE_Y * height);
455 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
456 /* lines for each second */
457 if (i % 5000000 == 0)
458 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
459 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
460 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
461 else if (i % 1000000 == 0)
462 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
463 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
464 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
466 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
467 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
471 static int analyze_plot(sd_bus *bus) {
472 struct unit_times *times;
473 struct boot_times *boot;
477 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
478 struct unit_times *u;
480 n = acquire_boot_times(bus, &boot);
484 n = pretty_boot_time(bus, &pretty_times);
488 get_os_name(&osname);
489 assert_se(uname(&name) >= 0);
491 n = acquire_time_data(bus, ×);
495 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
497 width = SCALE_X * (boot->firmware_time + boot->finish_time);
501 if (boot->firmware_time > boot->loader_time)
503 if (boot->loader_time) {
508 if (boot->initrd_time)
510 if (boot->kernel_time)
513 for (u = times; u < times + n; u++) {
514 double text_start, text_width;
516 if (u->activating < boot->userspace_time ||
517 u->activating > boot->finish_time) {
523 /* If the text cannot fit on the left side then
524 * increase the svg width so it fits on the right.
525 * TODO: calculate the text width more accurately */
526 text_width = 8.0 * strlen(u->name);
527 text_start = (boot->firmware_time + u->activating) * SCALE_X;
528 if (text_width > text_start && text_width + text_start > width)
529 width = text_width + text_start;
531 if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
532 && u->activated == 0 && u->deactivating == 0)
533 u->activated = u->deactivating = u->deactivated;
534 if (u->activated < u->activating || u->activated > boot->finish_time)
535 u->activated = boot->finish_time;
536 if (u->deactivating < u->activated || u->activated > boot->finish_time)
537 u->deactivating = boot->finish_time;
538 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
539 u->deactivated = boot->finish_time;
543 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
544 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
545 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
547 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
548 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
549 80.0 + width, 150.0 + (m * SCALE_Y) +
550 5 * SCALE_Y /* legend */);
552 /* write some basic info as a comment, including some help */
553 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
554 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
555 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
556 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
557 "<!-- point your browser to this file. -->\n\n"
558 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
561 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
562 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
563 " rect.background { fill: rgb(255,255,255); }\n"
564 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
565 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
566 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
567 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
568 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
569 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
570 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
571 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
572 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
573 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
574 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
575 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
577 " line.sec5 { stroke-width: 2; }\n"
578 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
579 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
580 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
581 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
582 " text.sec { font-size: 10px; }\n"
583 " ]]>\n </style>\n</defs>\n\n");
585 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
586 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
587 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
588 isempty(osname) ? "Linux" : osname,
589 name.nodename, name.release, name.version, name.machine);
591 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
592 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
594 if (boot->firmware_time) {
595 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
596 svg_text(true, -(double) boot->firmware_time, y, "firmware");
599 if (boot->loader_time) {
600 svg_bar("loader", -(double) boot->loader_time, 0, y);
601 svg_text(true, -(double) boot->loader_time, y, "loader");
604 if (boot->kernel_time) {
605 svg_bar("kernel", 0, boot->kernel_done_time, y);
606 svg_text(true, 0, y, "kernel");
609 if (boot->initrd_time) {
610 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
611 svg_text(true, boot->initrd_time, y, "initrd");
614 svg_bar("active", boot->userspace_time, boot->finish_time, y);
615 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
616 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
617 svg_text(true, boot->userspace_time, y, "systemd");
620 for (u = times; u < times + n; u++) {
621 char ts[FORMAT_TIMESPAN_MAX];
627 svg_bar("activating", u->activating, u->activated, y);
628 svg_bar("active", u->activated, u->deactivating, y);
629 svg_bar("deactivating", u->deactivating, u->deactivated, y);
631 /* place the text on the left if we have passed the half of the svg width */
632 b = u->activating * SCALE_X < width / 2;
634 svg_text(b, u->activating, y, "%s (%s)",
635 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
637 svg_text(b, u->activating, y, "%s", u->name);
643 svg_bar("activating", 0, 300000, y);
644 svg_text(true, 400000, y, "Activating");
646 svg_bar("active", 0, 300000, y);
647 svg_text(true, 400000, y, "Active");
649 svg_bar("deactivating", 0, 300000, y);
650 svg_text(true, 400000, y, "Deactivating");
652 svg_bar("generators", 0, 300000, y);
653 svg_text(true, 400000, y, "Generators");
655 svg_bar("unitsload", 0, 300000, y);
656 svg_text(true, 400000, y, "Loading unit files");
663 free_unit_times(times, (unsigned) n);
668 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
669 bool last, struct unit_times *times, struct boot_times *boot) {
671 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
673 for (i = level; i != 0; i--)
674 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
676 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
680 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
681 format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
682 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
683 else if (times->activated > boot->userspace_time)
684 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
687 } else printf("%s", name);
693 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
694 _cleanup_free_ char *path;
703 path = unit_dbus_path_from_name(name);
707 r = bus_get_unit_property_strv(bus, path, "After", &ret);
716 static Hashmap *unit_times_hashmap;
718 static int list_dependencies_compare(const void *_a, const void *_b) {
719 const char **a = (const char**) _a, **b = (const char**) _b;
720 usec_t usa = 0, usb = 0;
721 struct unit_times *times;
723 times = hashmap_get(unit_times_hashmap, *a);
725 usa = times->activated;
726 times = hashmap_get(unit_times_hashmap, *b);
728 usb = times->activated;
733 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
734 unsigned int branches) {
735 _cleanup_strv_free_ char **deps = NULL;
738 usec_t service_longest = 0;
740 struct unit_times *times;
741 struct boot_times *boot;
743 if(strv_extend(units, name))
746 r = list_dependencies_get_dependencies(bus, name, &deps);
750 qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
752 r = acquire_boot_times(bus, &boot);
756 STRV_FOREACH(c, deps) {
757 times = hashmap_get(unit_times_hashmap, *c);
760 && times->activated <= boot->finish_time
761 && (times->activated >= service_longest
762 || service_longest == 0)) {
763 service_longest = times->activated;
768 if (service_longest == 0 )
771 STRV_FOREACH(c, deps) {
772 times = hashmap_get(unit_times_hashmap, *c);
773 if (times && times->activated
774 && times->activated <= boot->finish_time
775 && (service_longest - times->activated) <= arg_fuzz) {
783 STRV_FOREACH(c, deps) {
784 times = hashmap_get(unit_times_hashmap, *c);
787 || times->activated > boot->finish_time
788 || service_longest - times->activated > arg_fuzz)
793 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
797 if (strv_contains(*units, *c)) {
798 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
805 r = list_dependencies_one(bus, *c, level + 1, units,
806 (branches << 1) | (to_print ? 1 : 0));
816 static int list_dependencies(sd_bus *bus, const char *name) {
817 _cleanup_strv_free_ char **units = NULL;
818 char ts[FORMAT_TIMESPAN_MAX];
819 struct unit_times *times;
821 const char *path, *id;
822 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
823 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
824 struct boot_times *boot;
828 path = unit_dbus_path_from_name(name);
832 r = sd_bus_get_property(
834 "org.freedesktop.systemd1",
836 "org.freedesktop.systemd1.Unit",
842 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
846 r = sd_bus_message_read(reply, "s", &id);
848 log_error("Failed to parse reply.");
852 times = hashmap_get(unit_times_hashmap, id);
854 r = acquire_boot_times(bus, &boot);
860 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
861 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
862 else if (times->activated > boot->userspace_time)
863 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
868 return list_dependencies_one(bus, name, 0, &units, 0);
871 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
872 struct unit_times *times;
877 n = acquire_time_data(bus, ×);
881 h = hashmap_new(string_hash_func, string_compare_func);
885 for (i = 0; i < (unsigned)n; i++) {
886 r = hashmap_put(h, times[i].name, ×[i]);
890 unit_times_hashmap = h;
892 pager_open_if_enabled();
894 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
895 "The time the unit takes to start is printed after the \"+\" character.\n");
897 if (!strv_isempty(names)) {
899 STRV_FOREACH(name, names)
900 list_dependencies(bus, *name);
902 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
905 free_unit_times(times, (unsigned) n);
909 static int analyze_blame(sd_bus *bus) {
910 struct unit_times *times;
914 n = acquire_time_data(bus, ×);
918 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
920 pager_open_if_enabled();
922 for (i = 0; i < (unsigned) n; i++) {
923 char ts[FORMAT_TIMESPAN_MAX];
925 if (times[i].time > 0)
926 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
929 free_unit_times(times, (unsigned) n);
933 static int analyze_time(sd_bus *bus) {
934 _cleanup_free_ char *buf = NULL;
937 r = pretty_boot_time(bus, &buf);
945 static int graph_one_property(sd_bus *bus, const struct unit_info *u, const char* prop, const char *color, char* patterns[]) {
946 _cleanup_strv_free_ char **units = NULL;
954 r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
958 STRV_FOREACH(unit, units) {
962 if (!strv_isempty(arg_dot_from_patterns)) {
965 STRV_FOREACH(p, arg_dot_from_patterns)
966 if (fnmatch(*p, u->id, 0) == 0) {
975 if (!strv_isempty(arg_dot_to_patterns)) {
978 STRV_FOREACH(p, arg_dot_to_patterns)
979 if (fnmatch(*p, *unit, 0) == 0) {
988 if (!strv_isempty(patterns)) {
991 STRV_FOREACH(p, patterns)
992 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
1000 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
1006 static int graph_one(sd_bus *bus, const struct unit_info *u, char *patterns[]) {
1012 if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
1013 r = graph_one_property(bus, u, "After", "green", patterns);
1018 if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
1019 r = graph_one_property(bus, u, "Requires", "black", patterns);
1022 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
1025 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
1028 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
1031 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
1034 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1042 static int dot(sd_bus *bus, char* patterns[]) {
1043 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1044 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1048 r = sd_bus_call_method(
1050 "org.freedesktop.systemd1",
1051 "/org/freedesktop/systemd1",
1052 "org.freedesktop.systemd1.Manager",
1058 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1062 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1066 printf("digraph systemd {\n");
1068 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1069 r = graph_one(bus, &u, patterns);
1076 log_info(" Color legend: black = Requires\n"
1077 " dark blue = Requisite\n"
1078 " dark grey = Wants\n"
1079 " red = Conflicts\n"
1080 " green = After\n");
1083 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1084 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1088 static int dump(sd_bus *bus, char **args) {
1089 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1090 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1091 const char *text = NULL;
1094 if (!strv_isempty(args)) {
1095 log_error("Too many arguments.");
1099 pager_open_if_enabled();
1101 r = sd_bus_call_method(
1103 "org.freedesktop.systemd1",
1104 "/org/freedesktop/systemd1",
1105 "org.freedesktop.systemd1.Manager",
1111 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1115 r = sd_bus_message_read(reply, "s", &text);
1117 log_error("Failed to parse reply");
1121 fputs(text, stdout);
1125 static int set_log_level(sd_bus *bus, char **args) {
1126 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1132 if (strv_length(args) != 1) {
1133 log_error("This command expects one argument only.");
1137 r = sd_bus_set_property(
1139 "org.freedesktop.systemd1",
1140 "/org/freedesktop/systemd1",
1141 "org.freedesktop.systemd1.Manager",
1147 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1154 static int help(void) {
1156 pager_open_if_enabled();
1158 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1159 "Process systemd profiling information.\n\n"
1160 " -h --help Show this help\n"
1161 " --version Show package version\n"
1162 " --system Connect to system manager\n"
1163 " --user Connect to user manager\n"
1164 " -H --host=[USER@]HOST Operate on remote host\n"
1165 " -M --machine=CONTAINER Operate on local container\n"
1166 " --order When generating a dependency graph, show only order\n"
1167 " --require When generating a dependency graph, show only requirement\n"
1168 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1169 " When generating a dependency graph, filter only origins\n"
1170 " or destinations, respectively\n"
1171 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1172 " services, which finished TIMESPAN earlier, than the\n"
1173 " latest in the branch. The unit of TIMESPAN is seconds\n"
1174 " unless specified with a different unit, i.e. 50ms\n"
1175 " --no-pager Do not pipe output into a pager\n\n"
1177 " time Print time spent in the kernel before reaching userspace\n"
1178 " blame Print list of running units ordered by time to init\n"
1179 " critical-chain Print a tree of the time critical chain of units\n"
1180 " plot Output SVG graphic showing service initialization\n"
1181 " dot Output dependency graph in dot(1) format\n"
1182 " set-log-level LEVEL Set logging threshold for systemd\n"
1183 " dump Output state serialization of service manager\n",
1184 program_invocation_short_name);
1186 /* When updating this list, including descriptions, apply
1187 * changes to shell-completion/bash/systemd and
1188 * shell-completion/systemd-zsh-completion.zsh too. */
1193 static int parse_argv(int argc, char *argv[]) {
1195 ARG_VERSION = 0x100,
1200 ARG_DOT_FROM_PATTERN,
1206 static const struct option options[] = {
1207 { "help", no_argument, NULL, 'h' },
1208 { "version", no_argument, NULL, ARG_VERSION },
1209 { "order", no_argument, NULL, ARG_ORDER },
1210 { "require", no_argument, NULL, ARG_REQUIRE },
1211 { "user", no_argument, NULL, ARG_USER },
1212 { "system", no_argument, NULL, ARG_SYSTEM },
1213 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1214 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1215 { "fuzz", required_argument, NULL, ARG_FUZZ },
1216 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1225 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1233 puts(PACKAGE_STRING);
1234 puts(SYSTEMD_FEATURES);
1246 arg_dot = DEP_ORDER;
1250 arg_dot = DEP_REQUIRE;
1253 case ARG_DOT_FROM_PATTERN:
1254 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1259 case ARG_DOT_TO_PATTERN:
1260 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1266 r = parse_sec(optarg, &arg_fuzz);
1272 arg_no_pager = true;
1276 arg_transport = BUS_TRANSPORT_REMOTE;
1281 arg_transport = BUS_TRANSPORT_CONTAINER;
1289 assert_not_reached("Unhandled option");
1296 int main(int argc, char *argv[]) {
1297 _cleanup_bus_unref_ sd_bus *bus = NULL;
1300 setlocale(LC_ALL, "");
1301 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1302 log_parse_environment();
1305 r = parse_argv(argc, argv);
1309 r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1311 log_error("Failed to create bus connection: %s", strerror(-r));
1315 if (!argv[optind] || streq(argv[optind], "time"))
1316 r = analyze_time(bus);
1317 else if (streq(argv[optind], "blame"))
1318 r = analyze_blame(bus);
1319 else if (streq(argv[optind], "critical-chain"))
1320 r = analyze_critical_chain(bus, argv+optind+1);
1321 else if (streq(argv[optind], "plot"))
1322 r = analyze_plot(bus);
1323 else if (streq(argv[optind], "dot"))
1324 r = dot(bus, argv+optind+1);
1325 else if (streq(argv[optind], "dump"))
1326 r = dump(bus, argv+optind+1);
1327 else if (streq(argv[optind], "set-log-level"))
1328 r = set_log_level(bus, argv+optind+1);
1330 log_error("Unknown operation '%s'.", argv[optind]);
1335 strv_free(arg_dot_from_patterns);
1336 strv_free(arg_dot_to_patterns);
1338 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;