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 security_start_time;
87 usec_t security_finish_time;
88 usec_t generators_start_time;
89 usec_t generators_finish_time;
90 usec_t unitsload_start_time;
91 usec_t unitsload_finish_time;
103 static void pager_open_if_enabled(void) {
111 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
112 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
121 r = sd_bus_get_property_trivial(
123 "org.freedesktop.systemd1",
131 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
138 static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
139 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
147 r = sd_bus_get_property_strv(
149 "org.freedesktop.systemd1",
151 "org.freedesktop.systemd1.Unit",
156 log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
163 static int compare_unit_time(const void *a, const void *b) {
164 return compare(((struct unit_times *)b)->time,
165 ((struct unit_times *)a)->time);
168 static int compare_unit_start(const void *a, const void *b) {
169 return compare(((struct unit_times *)a)->activating,
170 ((struct unit_times *)b)->activating);
173 static int get_os_name(char **_n) {
177 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
188 static void free_unit_times(struct unit_times *t, unsigned n) {
189 struct unit_times *p;
191 for (p = t; p < t + n; p++)
197 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
198 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
199 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
201 struct unit_times *unit_times = NULL;
205 r = sd_bus_call_method(
207 "org.freedesktop.systemd1",
208 "/org/freedesktop/systemd1",
209 "org.freedesktop.systemd1.Manager",
214 log_error("Failed to list units: %s", bus_error_message(&error, -r));
218 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
220 bus_log_parse_error(r);
224 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
225 struct unit_times *t;
227 if (!GREEDY_REALLOC(unit_times, size, c+1)) {
235 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
237 if (bus_get_uint64_property(bus, u.unit_path,
238 "org.freedesktop.systemd1.Unit",
239 "InactiveExitTimestampMonotonic",
240 &t->activating) < 0 ||
241 bus_get_uint64_property(bus, u.unit_path,
242 "org.freedesktop.systemd1.Unit",
243 "ActiveEnterTimestampMonotonic",
244 &t->activated) < 0 ||
245 bus_get_uint64_property(bus, u.unit_path,
246 "org.freedesktop.systemd1.Unit",
247 "ActiveExitTimestampMonotonic",
248 &t->deactivating) < 0 ||
249 bus_get_uint64_property(bus, u.unit_path,
250 "org.freedesktop.systemd1.Unit",
251 "InactiveEnterTimestampMonotonic",
252 &t->deactivated) < 0) {
257 if (t->activated >= t->activating)
258 t->time = t->activated - t->activating;
259 else if (t->deactivated >= t->activating)
260 t->time = t->deactivated - t->activating;
264 if (t->activating == 0)
267 t->name = strdup(u.id);
268 if (t->name == NULL) {
275 bus_log_parse_error(r);
283 free_unit_times(unit_times, (unsigned) c);
287 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
288 static struct boot_times times;
289 static bool cached = false;
294 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
296 if (bus_get_uint64_property(bus,
297 "/org/freedesktop/systemd1",
298 "org.freedesktop.systemd1.Manager",
299 "FirmwareTimestampMonotonic",
300 ×.firmware_time) < 0 ||
301 bus_get_uint64_property(bus,
302 "/org/freedesktop/systemd1",
303 "org.freedesktop.systemd1.Manager",
304 "LoaderTimestampMonotonic",
305 ×.loader_time) < 0 ||
306 bus_get_uint64_property(bus,
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
310 ×.kernel_time) < 0 ||
311 bus_get_uint64_property(bus,
312 "/org/freedesktop/systemd1",
313 "org.freedesktop.systemd1.Manager",
314 "InitRDTimestampMonotonic",
315 ×.initrd_time) < 0 ||
316 bus_get_uint64_property(bus,
317 "/org/freedesktop/systemd1",
318 "org.freedesktop.systemd1.Manager",
319 "UserspaceTimestampMonotonic",
320 ×.userspace_time) < 0 ||
321 bus_get_uint64_property(bus,
322 "/org/freedesktop/systemd1",
323 "org.freedesktop.systemd1.Manager",
324 "FinishTimestampMonotonic",
325 ×.finish_time) < 0 ||
326 bus_get_uint64_property(bus,
327 "/org/freedesktop/systemd1",
328 "org.freedesktop.systemd1.Manager",
329 "SecurityStartTimestampMonotonic",
330 ×.security_start_time) < 0 ||
331 bus_get_uint64_property(bus,
332 "/org/freedesktop/systemd1",
333 "org.freedesktop.systemd1.Manager",
334 "SecurityFinishTimestampMonotonic",
335 ×.security_finish_time) < 0 ||
336 bus_get_uint64_property(bus,
337 "/org/freedesktop/systemd1",
338 "org.freedesktop.systemd1.Manager",
339 "GeneratorsStartTimestampMonotonic",
340 ×.generators_start_time) < 0 ||
341 bus_get_uint64_property(bus,
342 "/org/freedesktop/systemd1",
343 "org.freedesktop.systemd1.Manager",
344 "GeneratorsFinishTimestampMonotonic",
345 ×.generators_finish_time) < 0 ||
346 bus_get_uint64_property(bus,
347 "/org/freedesktop/systemd1",
348 "org.freedesktop.systemd1.Manager",
349 "UnitsLoadStartTimestampMonotonic",
350 ×.unitsload_start_time) < 0 ||
351 bus_get_uint64_property(bus,
352 "/org/freedesktop/systemd1",
353 "org.freedesktop.systemd1.Manager",
354 "UnitsLoadFinishTimestampMonotonic",
355 ×.unitsload_finish_time) < 0)
358 if (times.finish_time <= 0) {
359 log_error("Bootup is not yet finished. Please try again later.");
363 if (times.initrd_time)
364 times.kernel_done_time = times.initrd_time;
366 times.kernel_done_time = times.userspace_time;
375 static int pretty_boot_time(sd_bus *bus, char **_buf) {
376 char ts[FORMAT_TIMESPAN_MAX];
377 struct boot_times *t;
378 static char buf[4096];
383 r = acquire_boot_times(bus, &t);
390 size = strpcpyf(&ptr, size, "Startup finished in ");
391 if (t->firmware_time)
392 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
394 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
396 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
397 if (t->initrd_time > 0)
398 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
400 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
401 if (t->kernel_time > 0)
402 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
404 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
414 static void svg_graph_box(double height, double begin, double end) {
417 /* outside box, fill */
418 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
419 SCALE_X * (end - begin), SCALE_Y * height);
421 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
422 /* lines for each second */
423 if (i % 5000000 == 0)
424 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
425 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
426 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
427 else if (i % 1000000 == 0)
428 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
429 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
430 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
432 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
433 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
437 static int analyze_plot(sd_bus *bus) {
438 struct unit_times *times;
439 struct boot_times *boot;
443 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
444 struct unit_times *u;
446 n = acquire_boot_times(bus, &boot);
450 n = pretty_boot_time(bus, &pretty_times);
454 get_os_name(&osname);
455 assert_se(uname(&name) >= 0);
457 n = acquire_time_data(bus, ×);
461 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
463 width = SCALE_X * (boot->firmware_time + boot->finish_time);
467 if (boot->firmware_time > boot->loader_time)
469 if (boot->loader_time) {
474 if (boot->initrd_time)
476 if (boot->kernel_time)
479 for (u = times; u < times + n; u++) {
480 double text_start, text_width;
482 if (u->activating < boot->userspace_time ||
483 u->activating > boot->finish_time) {
489 /* If the text cannot fit on the left side then
490 * increase the svg width so it fits on the right.
491 * TODO: calculate the text width more accurately */
492 text_width = 8.0 * strlen(u->name);
493 text_start = (boot->firmware_time + u->activating) * SCALE_X;
494 if (text_width > text_start && text_width + text_start > width)
495 width = text_width + text_start;
497 if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
498 && u->activated == 0 && u->deactivating == 0)
499 u->activated = u->deactivating = u->deactivated;
500 if (u->activated < u->activating || u->activated > boot->finish_time)
501 u->activated = boot->finish_time;
502 if (u->deactivating < u->activated || u->activated > boot->finish_time)
503 u->deactivating = boot->finish_time;
504 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
505 u->deactivated = boot->finish_time;
509 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
510 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
511 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
513 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
514 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
515 80.0 + width, 150.0 + (m * SCALE_Y) +
516 5 * SCALE_Y /* legend */);
518 /* write some basic info as a comment, including some help */
519 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
520 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
521 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
522 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
523 "<!-- point your browser to this file. -->\n\n"
524 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
527 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
528 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
529 " rect.background { fill: rgb(255,255,255); }\n"
530 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
531 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
532 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
533 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
534 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
535 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
536 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
537 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
538 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
539 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
540 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
541 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
542 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
544 " line.sec5 { stroke-width: 2; }\n"
545 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
546 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
547 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
548 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
549 " text.sec { font-size: 10px; }\n"
550 " ]]>\n </style>\n</defs>\n\n");
552 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
553 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
554 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
555 isempty(osname) ? "Linux" : osname,
556 name.nodename, name.release, name.version, name.machine);
558 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
559 svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
561 if (boot->firmware_time) {
562 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
563 svg_text(true, -(double) boot->firmware_time, y, "firmware");
566 if (boot->loader_time) {
567 svg_bar("loader", -(double) boot->loader_time, 0, y);
568 svg_text(true, -(double) boot->loader_time, y, "loader");
571 if (boot->kernel_time) {
572 svg_bar("kernel", 0, boot->kernel_done_time, y);
573 svg_text(true, 0, y, "kernel");
576 if (boot->initrd_time) {
577 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
578 svg_text(true, boot->initrd_time, y, "initrd");
581 svg_bar("active", boot->userspace_time, boot->finish_time, y);
582 svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
583 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
584 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
585 svg_text(true, boot->userspace_time, y, "systemd");
588 for (u = times; u < times + n; u++) {
589 char ts[FORMAT_TIMESPAN_MAX];
595 svg_bar("activating", u->activating, u->activated, y);
596 svg_bar("active", u->activated, u->deactivating, y);
597 svg_bar("deactivating", u->deactivating, u->deactivated, y);
599 /* place the text on the left if we have passed the half of the svg width */
600 b = u->activating * SCALE_X < width / 2;
602 svg_text(b, u->activating, y, "%s (%s)",
603 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
605 svg_text(b, u->activating, y, "%s", u->name);
612 svg("<g transform=\"translate(20,100)\">\n");
614 svg_bar("activating", 0, 300000, y);
615 svg_text(true, 400000, y, "Activating");
617 svg_bar("active", 0, 300000, y);
618 svg_text(true, 400000, y, "Active");
620 svg_bar("deactivating", 0, 300000, y);
621 svg_text(true, 400000, y, "Deactivating");
623 svg_bar("security", 0, 300000, y);
624 svg_text(true, 400000, y, "Setting up security module");
626 svg_bar("generators", 0, 300000, y);
627 svg_text(true, 400000, y, "Generators");
629 svg_bar("unitsload", 0, 300000, y);
630 svg_text(true, 400000, y, "Loading unit files");
637 free_unit_times(times, (unsigned) n);
642 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
643 bool last, struct unit_times *times, struct boot_times *boot) {
645 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
647 for (i = level; i != 0; i--)
648 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
650 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
654 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
655 format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
656 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
657 else if (times->activated > boot->userspace_time)
658 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
668 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
669 _cleanup_free_ char *path = NULL;
675 path = unit_dbus_path_from_name(name);
679 return bus_get_unit_property_strv(bus, path, "After", deps);
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);
691 usa = times->activated;
692 times = hashmap_get(unit_times_hashmap, *b);
694 usb = times->activated;
699 static int list_dependencies_one(sd_bus *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_safe(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->activated <= boot->finish_time
727 && (times->activated >= service_longest
728 || service_longest == 0)) {
729 service_longest = times->activated;
734 if (service_longest == 0 )
737 STRV_FOREACH(c, deps) {
738 times = hashmap_get(unit_times_hashmap, *c);
739 if (times && times->activated
740 && times->activated <= boot->finish_time
741 && (service_longest - times->activated) <= arg_fuzz) {
749 STRV_FOREACH(c, deps) {
750 times = hashmap_get(unit_times_hashmap, *c);
753 || times->activated > boot->finish_time
754 || service_longest - times->activated > 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),
771 r = list_dependencies_one(bus, *c, level + 1, units,
772 (branches << 1) | (to_print ? 1 : 0));
782 static int list_dependencies(sd_bus *bus, const char *name) {
783 _cleanup_strv_free_ char **units = NULL;
784 char ts[FORMAT_TIMESPAN_MAX];
785 struct unit_times *times;
787 const char *path, *id;
788 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
789 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
790 struct boot_times *boot;
794 path = unit_dbus_path_from_name(name);
798 r = sd_bus_get_property(
800 "org.freedesktop.systemd1",
802 "org.freedesktop.systemd1.Unit",
808 log_error("Failed to get ID: %s", bus_error_message(&error, -r));
812 r = sd_bus_message_read(reply, "s", &id);
814 return bus_log_parse_error(r);
816 times = hashmap_get(unit_times_hashmap, id);
818 r = acquire_boot_times(bus, &boot);
824 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
825 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
826 else if (times->activated > boot->userspace_time)
827 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
832 return list_dependencies_one(bus, name, 0, &units, 0);
835 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
836 struct unit_times *times;
841 n = acquire_time_data(bus, ×);
845 h = hashmap_new(string_hash_func, string_compare_func);
849 for (i = 0; i < (unsigned)n; i++) {
850 r = hashmap_put(h, times[i].name, ×[i]);
854 unit_times_hashmap = h;
856 pager_open_if_enabled();
858 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
859 "The time the unit takes to start is printed after the \"+\" character.\n");
861 if (!strv_isempty(names)) {
863 STRV_FOREACH(name, names)
864 list_dependencies(bus, *name);
866 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
869 free_unit_times(times, (unsigned) n);
873 static int analyze_blame(sd_bus *bus) {
874 struct unit_times *times;
878 n = acquire_time_data(bus, ×);
882 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
884 pager_open_if_enabled();
886 for (i = 0; i < (unsigned) n; i++) {
887 char ts[FORMAT_TIMESPAN_MAX];
889 if (times[i].time > 0)
890 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
893 free_unit_times(times, (unsigned) n);
897 static int analyze_time(sd_bus *bus) {
898 _cleanup_free_ char *buf = NULL;
901 r = pretty_boot_time(bus, &buf);
909 static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[]) {
910 _cleanup_strv_free_ char **units = NULL;
918 r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
922 STRV_FOREACH(unit, units) {
926 if (!strv_isempty(arg_dot_from_patterns)) {
929 STRV_FOREACH(p, arg_dot_from_patterns)
930 if (fnmatch(*p, u->id, 0) == 0) {
939 if (!strv_isempty(arg_dot_to_patterns)) {
942 STRV_FOREACH(p, arg_dot_to_patterns)
943 if (fnmatch(*p, *unit, 0) == 0) {
952 if (!strv_isempty(patterns)) {
955 STRV_FOREACH(p, patterns)
956 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
964 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
970 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[]) {
976 if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
977 r = graph_one_property(bus, u, "After", "green", patterns);
982 if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
983 r = graph_one_property(bus, u, "Requires", "black", patterns);
986 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
989 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
992 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
995 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
998 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1006 static int dot(sd_bus *bus, char* patterns[]) {
1007 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1008 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1012 r = sd_bus_call_method(
1014 "org.freedesktop.systemd1",
1015 "/org/freedesktop/systemd1",
1016 "org.freedesktop.systemd1.Manager",
1022 log_error("Failed to list units: %s", bus_error_message(&error, -r));
1026 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1028 return bus_log_parse_error(r);
1030 printf("digraph systemd {\n");
1032 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1034 r = graph_one(bus, &u, patterns);
1039 return bus_log_parse_error(r);
1043 log_info(" Color legend: black = Requires\n"
1044 " dark blue = Requisite\n"
1045 " dark grey = Wants\n"
1046 " red = Conflicts\n"
1047 " green = After\n");
1050 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1051 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1056 static int dump(sd_bus *bus, char **args) {
1057 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1058 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1059 const char *text = NULL;
1062 if (!strv_isempty(args)) {
1063 log_error("Too many arguments.");
1067 pager_open_if_enabled();
1069 r = sd_bus_call_method(
1071 "org.freedesktop.systemd1",
1072 "/org/freedesktop/systemd1",
1073 "org.freedesktop.systemd1.Manager",
1079 log_error("Failed issue method call: %s", bus_error_message(&error, -r));
1083 r = sd_bus_message_read(reply, "s", &text);
1085 return bus_log_parse_error(r);
1087 fputs(text, stdout);
1091 static int set_log_level(sd_bus *bus, char **args) {
1092 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1098 if (strv_length(args) != 1) {
1099 log_error("This command expects one argument only.");
1103 r = sd_bus_set_property(
1105 "org.freedesktop.systemd1",
1106 "/org/freedesktop/systemd1",
1107 "org.freedesktop.systemd1.Manager",
1113 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1120 static int help(void) {
1122 pager_open_if_enabled();
1124 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1125 "Process systemd profiling information.\n\n"
1126 " -h --help Show this help\n"
1127 " --version Show package version\n"
1128 " --no-pager Do not pipe output into a pager\n"
1129 " --system Connect to system manager\n"
1130 " --user Connect to user manager\n"
1131 " -H --host=[USER@]HOST Operate on remote host\n"
1132 " -M --machine=CONTAINER Operate on local container\n"
1133 " --order When generating a dependency graph, show only order\n"
1134 " --require When generating a dependency graph, show only requirement\n"
1135 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1136 " When generating a dependency graph, filter only origins\n"
1137 " or destinations, respectively\n"
1138 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1139 " services, which finished TIMESPAN earlier, than the\n"
1140 " latest in the branch. The unit of TIMESPAN is seconds\n"
1141 " unless specified with a different unit, i.e. 50ms\n\n"
1143 " time Print time spent in the kernel before reaching userspace\n"
1144 " blame Print list of running units ordered by time to init\n"
1145 " critical-chain Print a tree of the time critical chain of units\n"
1146 " plot Output SVG graphic showing service initialization\n"
1147 " dot Output dependency graph in dot(1) format\n"
1148 " set-log-level LEVEL Set logging threshold for systemd\n"
1149 " dump Output state serialization of service manager\n",
1150 program_invocation_short_name);
1152 /* When updating this list, including descriptions, apply
1153 * changes to shell-completion/bash/systemd and
1154 * shell-completion/systemd-zsh-completion.zsh too. */
1159 static int parse_argv(int argc, char *argv[]) {
1161 ARG_VERSION = 0x100,
1166 ARG_DOT_FROM_PATTERN,
1172 static const struct option options[] = {
1173 { "help", no_argument, NULL, 'h' },
1174 { "version", no_argument, NULL, ARG_VERSION },
1175 { "order", no_argument, NULL, ARG_ORDER },
1176 { "require", no_argument, NULL, ARG_REQUIRE },
1177 { "user", no_argument, NULL, ARG_USER },
1178 { "system", no_argument, NULL, ARG_SYSTEM },
1179 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1180 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1181 { "fuzz", required_argument, NULL, ARG_FUZZ },
1182 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1183 { "host", required_argument, NULL, 'H' },
1184 { "machine", required_argument, NULL, 'M' },
1193 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1201 puts(PACKAGE_STRING);
1202 puts(SYSTEMD_FEATURES);
1214 arg_dot = DEP_ORDER;
1218 arg_dot = DEP_REQUIRE;
1221 case ARG_DOT_FROM_PATTERN:
1222 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1227 case ARG_DOT_TO_PATTERN:
1228 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1234 r = parse_sec(optarg, &arg_fuzz);
1240 arg_no_pager = true;
1244 arg_transport = BUS_TRANSPORT_REMOTE;
1249 arg_transport = BUS_TRANSPORT_CONTAINER;
1257 assert_not_reached("Unhandled option");
1264 int main(int argc, char *argv[]) {
1265 _cleanup_bus_unref_ sd_bus *bus = NULL;
1268 setlocale(LC_ALL, "");
1269 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1270 log_parse_environment();
1273 r = parse_argv(argc, argv);
1277 r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1279 log_error("Failed to create bus connection: %s", strerror(-r));
1283 if (!argv[optind] || streq(argv[optind], "time"))
1284 r = analyze_time(bus);
1285 else if (streq(argv[optind], "blame"))
1286 r = analyze_blame(bus);
1287 else if (streq(argv[optind], "critical-chain"))
1288 r = analyze_critical_chain(bus, argv+optind+1);
1289 else if (streq(argv[optind], "plot"))
1290 r = analyze_plot(bus);
1291 else if (streq(argv[optind], "dot"))
1292 r = dot(bus, argv+optind+1);
1293 else if (streq(argv[optind], "dump"))
1294 r = dump(bus, argv+optind+1);
1295 else if (streq(argv[optind], "set-log-level"))
1296 r = set_log_level(bus, argv+optind+1);
1298 log_error("Unknown operation '%s'.", argv[optind]);
1303 strv_free(arg_dot_from_patterns);
1304 strv_free(arg_dot_to_patterns);
1306 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;