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 return bus_log_parse_error(r);
199 static int bus_get_unit_property_strv(sd_bus *bus, const char *unit_path, const char *prop, char ***strv) {
200 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
201 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
205 r = sd_bus_get_property(
207 "org.freedesktop.systemd1",
209 "org.freedesktop.systemd1.Unit",
215 log_error("Failed to get unit property: %s %s", prop, bus_error_message(&error, -r));
219 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s");
223 while ((r = sd_bus_message_read(reply, "s", &s)) > 0) {
224 r = strv_extend(strv, s);
232 return bus_log_parse_error(r);
237 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
238 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
239 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
240 int r, c = 0, n_units = 0;
241 struct unit_times *unit_times = NULL;
244 r = sd_bus_call_method(
246 "org.freedesktop.systemd1",
247 "/org/freedesktop/systemd1",
248 "org.freedesktop.systemd1.Manager",
254 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
258 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
262 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
263 struct unit_times *t;
269 struct unit_times *w;
271 n_units = MAX(2*c, 16);
272 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
284 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
286 if (bus_get_uint64_property(bus, u.unit_path,
287 "org.freedesktop.systemd1.Unit",
288 "InactiveExitTimestampMonotonic",
289 &t->activating) < 0 ||
290 bus_get_uint64_property(bus, u.unit_path,
291 "org.freedesktop.systemd1.Unit",
292 "ActiveEnterTimestampMonotonic",
293 &t->activated) < 0 ||
294 bus_get_uint64_property(bus, u.unit_path,
295 "org.freedesktop.systemd1.Unit",
296 "ActiveExitTimestampMonotonic",
297 &t->deactivating) < 0 ||
298 bus_get_uint64_property(bus, u.unit_path,
299 "org.freedesktop.systemd1.Unit",
300 "InactiveEnterTimestampMonotonic",
301 &t->deactivated) < 0) {
306 if (t->activated >= t->activating)
307 t->time = t->activated - t->activating;
308 else if (t->deactivated >= t->activating)
309 t->time = t->deactivated - t->activating;
313 if (t->activating == 0)
316 t->name = strdup(u.id);
317 if (t->name == NULL) {
328 free_unit_times(unit_times, (unsigned) c);
332 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
333 static struct boot_times times;
334 static bool cached = false;
339 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
341 if (bus_get_uint64_property(bus,
342 "/org/freedesktop/systemd1",
343 "org.freedesktop.systemd1.Manager",
344 "FirmwareTimestampMonotonic",
345 ×.firmware_time) < 0 ||
346 bus_get_uint64_property(bus,
347 "/org/freedesktop/systemd1",
348 "org.freedesktop.systemd1.Manager",
349 "LoaderTimestampMonotonic",
350 ×.loader_time) < 0 ||
351 bus_get_uint64_property(bus,
352 "/org/freedesktop/systemd1",
353 "org.freedesktop.systemd1.Manager",
355 ×.kernel_time) < 0 ||
356 bus_get_uint64_property(bus,
357 "/org/freedesktop/systemd1",
358 "org.freedesktop.systemd1.Manager",
359 "InitRDTimestampMonotonic",
360 ×.initrd_time) < 0 ||
361 bus_get_uint64_property(bus,
362 "/org/freedesktop/systemd1",
363 "org.freedesktop.systemd1.Manager",
364 "UserspaceTimestampMonotonic",
365 ×.userspace_time) < 0 ||
366 bus_get_uint64_property(bus,
367 "/org/freedesktop/systemd1",
368 "org.freedesktop.systemd1.Manager",
369 "FinishTimestampMonotonic",
370 ×.finish_time) < 0 ||
371 bus_get_uint64_property(bus,
372 "/org/freedesktop/systemd1",
373 "org.freedesktop.systemd1.Manager",
374 "GeneratorsStartTimestampMonotonic",
375 ×.generators_start_time) < 0 ||
376 bus_get_uint64_property(bus,
377 "/org/freedesktop/systemd1",
378 "org.freedesktop.systemd1.Manager",
379 "GeneratorsFinishTimestampMonotonic",
380 ×.generators_finish_time) < 0 ||
381 bus_get_uint64_property(bus,
382 "/org/freedesktop/systemd1",
383 "org.freedesktop.systemd1.Manager",
384 "UnitsLoadStartTimestampMonotonic",
385 ×.unitsload_start_time) < 0 ||
386 bus_get_uint64_property(bus,
387 "/org/freedesktop/systemd1",
388 "org.freedesktop.systemd1.Manager",
389 "UnitsLoadFinishTimestampMonotonic",
390 ×.unitsload_finish_time) < 0)
393 if (times.finish_time <= 0) {
394 log_error("Bootup is not yet finished. Please try again later.");
398 if (times.initrd_time)
399 times.kernel_done_time = times.initrd_time;
401 times.kernel_done_time = times.userspace_time;
410 static int pretty_boot_time(sd_bus *bus, char **_buf) {
411 char ts[FORMAT_TIMESPAN_MAX];
412 struct boot_times *t;
413 static char buf[4096];
418 r = acquire_boot_times(bus, &t);
425 size = strpcpyf(&ptr, size, "Startup finished in ");
426 if (t->firmware_time)
427 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
429 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
431 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
432 if (t->initrd_time > 0)
433 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
435 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
436 if (t->kernel_time > 0)
437 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
439 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
449 static void svg_graph_box(double height, double begin, double end) {
452 /* outside box, fill */
453 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
454 SCALE_X * (end - begin), SCALE_Y * height);
456 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
457 /* lines for each second */
458 if (i % 5000000 == 0)
459 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
460 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
461 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
462 else if (i % 1000000 == 0)
463 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
464 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
465 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
467 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
468 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
472 static int analyze_plot(sd_bus *bus) {
473 struct unit_times *times;
474 struct boot_times *boot;
478 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
479 struct unit_times *u;
481 n = acquire_boot_times(bus, &boot);
485 n = pretty_boot_time(bus, &pretty_times);
489 get_os_name(&osname);
490 assert_se(uname(&name) >= 0);
492 n = acquire_time_data(bus, ×);
496 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
498 width = SCALE_X * (boot->firmware_time + boot->finish_time);
502 if (boot->firmware_time > boot->loader_time)
504 if (boot->loader_time) {
509 if (boot->initrd_time)
511 if (boot->kernel_time)
514 for (u = times; u < times + n; u++) {
515 double text_start, text_width;
517 if (u->activating < boot->userspace_time ||
518 u->activating > boot->finish_time) {
524 /* If the text cannot fit on the left side then
525 * increase the svg width so it fits on the right.
526 * TODO: calculate the text width more accurately */
527 text_width = 8.0 * strlen(u->name);
528 text_start = (boot->firmware_time + u->activating) * SCALE_X;
529 if (text_width > text_start && text_width + text_start > width)
530 width = text_width + text_start;
532 if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
533 && u->activated == 0 && u->deactivating == 0)
534 u->activated = u->deactivating = u->deactivated;
535 if (u->activated < u->activating || u->activated > boot->finish_time)
536 u->activated = boot->finish_time;
537 if (u->deactivating < u->activated || u->activated > boot->finish_time)
538 u->deactivating = boot->finish_time;
539 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
540 u->deactivated = boot->finish_time;
544 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
545 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
546 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
548 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
549 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
550 80.0 + width, 150.0 + (m * SCALE_Y) +
551 5 * SCALE_Y /* legend */);
553 /* write some basic info as a comment, including some help */
554 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
555 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
556 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
557 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
558 "<!-- point your browser to this file. -->\n\n"
559 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
562 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
563 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
564 " rect.background { fill: rgb(255,255,255); }\n"
565 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
566 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
567 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
568 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
569 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
570 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
571 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
572 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
573 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
574 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
575 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
576 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
578 " line.sec5 { stroke-width: 2; }\n"
579 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
580 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
581 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
582 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
583 " text.sec { font-size: 10px; }\n"
584 " ]]>\n </style>\n</defs>\n\n");
586 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
587 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
588 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
589 isempty(osname) ? "Linux" : osname,
590 name.nodename, name.release, name.version, name.machine);
592 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
593 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
595 if (boot->firmware_time) {
596 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
597 svg_text(true, -(double) boot->firmware_time, y, "firmware");
600 if (boot->loader_time) {
601 svg_bar("loader", -(double) boot->loader_time, 0, y);
602 svg_text(true, -(double) boot->loader_time, y, "loader");
605 if (boot->kernel_time) {
606 svg_bar("kernel", 0, boot->kernel_done_time, y);
607 svg_text(true, 0, y, "kernel");
610 if (boot->initrd_time) {
611 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
612 svg_text(true, boot->initrd_time, y, "initrd");
615 svg_bar("active", boot->userspace_time, boot->finish_time, y);
616 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
617 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
618 svg_text(true, boot->userspace_time, y, "systemd");
621 for (u = times; u < times + n; u++) {
622 char ts[FORMAT_TIMESPAN_MAX];
628 svg_bar("activating", u->activating, u->activated, y);
629 svg_bar("active", u->activated, u->deactivating, y);
630 svg_bar("deactivating", u->deactivating, u->deactivated, y);
632 /* place the text on the left if we have passed the half of the svg width */
633 b = u->activating * SCALE_X < width / 2;
635 svg_text(b, u->activating, y, "%s (%s)",
636 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
638 svg_text(b, u->activating, y, "%s", u->name);
644 svg_bar("activating", 0, 300000, y);
645 svg_text(true, 400000, y, "Activating");
647 svg_bar("active", 0, 300000, y);
648 svg_text(true, 400000, y, "Active");
650 svg_bar("deactivating", 0, 300000, y);
651 svg_text(true, 400000, y, "Deactivating");
653 svg_bar("generators", 0, 300000, y);
654 svg_text(true, 400000, y, "Generators");
656 svg_bar("unitsload", 0, 300000, y);
657 svg_text(true, 400000, y, "Loading unit files");
664 free_unit_times(times, (unsigned) n);
669 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
670 bool last, struct unit_times *times, struct boot_times *boot) {
672 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
674 for (i = level; i != 0; i--)
675 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
677 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
681 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
682 format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
683 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
684 else if (times->activated > boot->userspace_time)
685 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
688 } else printf("%s", name);
694 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
695 _cleanup_free_ char *path;
704 path = unit_dbus_path_from_name(name);
708 r = bus_get_unit_property_strv(bus, path, "After", &ret);
717 static Hashmap *unit_times_hashmap;
719 static int list_dependencies_compare(const void *_a, const void *_b) {
720 const char **a = (const char**) _a, **b = (const char**) _b;
721 usec_t usa = 0, usb = 0;
722 struct unit_times *times;
724 times = hashmap_get(unit_times_hashmap, *a);
726 usa = times->activated;
727 times = hashmap_get(unit_times_hashmap, *b);
729 usb = times->activated;
734 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
735 unsigned int branches) {
736 _cleanup_strv_free_ char **deps = NULL;
739 usec_t service_longest = 0;
741 struct unit_times *times;
742 struct boot_times *boot;
744 if(strv_extend(units, name))
747 r = list_dependencies_get_dependencies(bus, name, &deps);
751 qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
753 r = acquire_boot_times(bus, &boot);
757 STRV_FOREACH(c, deps) {
758 times = hashmap_get(unit_times_hashmap, *c);
761 && times->activated <= boot->finish_time
762 && (times->activated >= service_longest
763 || service_longest == 0)) {
764 service_longest = times->activated;
769 if (service_longest == 0 )
772 STRV_FOREACH(c, deps) {
773 times = hashmap_get(unit_times_hashmap, *c);
774 if (times && times->activated
775 && times->activated <= boot->finish_time
776 && (service_longest - times->activated) <= arg_fuzz) {
784 STRV_FOREACH(c, deps) {
785 times = hashmap_get(unit_times_hashmap, *c);
788 || times->activated > boot->finish_time
789 || service_longest - times->activated > arg_fuzz)
794 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
798 if (strv_contains(*units, *c)) {
799 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
806 r = list_dependencies_one(bus, *c, level + 1, units,
807 (branches << 1) | (to_print ? 1 : 0));
817 static int list_dependencies(sd_bus *bus, const char *name) {
818 _cleanup_strv_free_ char **units = NULL;
819 char ts[FORMAT_TIMESPAN_MAX];
820 struct unit_times *times;
822 const char *path, *id;
823 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
824 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
825 struct boot_times *boot;
829 path = unit_dbus_path_from_name(name);
833 r = sd_bus_get_property(
835 "org.freedesktop.systemd1",
837 "org.freedesktop.systemd1.Unit",
843 log_error("Failed to get ID: %s", bus_error_message(&error, -r));
847 r = sd_bus_message_read(reply, "s", &id);
849 return bus_log_parse_error(r);
851 times = hashmap_get(unit_times_hashmap, id);
853 r = acquire_boot_times(bus, &boot);
859 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
860 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
861 else if (times->activated > boot->userspace_time)
862 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
867 return list_dependencies_one(bus, name, 0, &units, 0);
870 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
871 struct unit_times *times;
876 n = acquire_time_data(bus, ×);
880 h = hashmap_new(string_hash_func, string_compare_func);
884 for (i = 0; i < (unsigned)n; i++) {
885 r = hashmap_put(h, times[i].name, ×[i]);
889 unit_times_hashmap = h;
891 pager_open_if_enabled();
893 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
894 "The time the unit takes to start is printed after the \"+\" character.\n");
896 if (!strv_isempty(names)) {
898 STRV_FOREACH(name, names)
899 list_dependencies(bus, *name);
901 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
904 free_unit_times(times, (unsigned) n);
908 static int analyze_blame(sd_bus *bus) {
909 struct unit_times *times;
913 n = acquire_time_data(bus, ×);
917 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
919 pager_open_if_enabled();
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(sd_bus *bus) {
933 _cleanup_free_ char *buf = NULL;
936 r = pretty_boot_time(bus, &buf);
944 static int graph_one_property(sd_bus *bus, const struct unit_info *u, const char* prop, const char *color, char* patterns[]) {
945 _cleanup_strv_free_ char **units = NULL;
953 r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
957 STRV_FOREACH(unit, units) {
961 if (!strv_isempty(arg_dot_from_patterns)) {
964 STRV_FOREACH(p, arg_dot_from_patterns)
965 if (fnmatch(*p, u->id, 0) == 0) {
974 if (!strv_isempty(arg_dot_to_patterns)) {
977 STRV_FOREACH(p, arg_dot_to_patterns)
978 if (fnmatch(*p, *unit, 0) == 0) {
987 if (!strv_isempty(patterns)) {
990 STRV_FOREACH(p, patterns)
991 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
999 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
1005 static int graph_one(sd_bus *bus, const struct unit_info *u, char *patterns[]) {
1011 if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
1012 r = graph_one_property(bus, u, "After", "green", patterns);
1017 if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
1018 r = graph_one_property(bus, u, "Requires", "black", patterns);
1021 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
1024 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
1027 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
1030 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
1033 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1041 static int dot(sd_bus *bus, char* patterns[]) {
1042 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1043 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1047 r = sd_bus_call_method(
1049 "org.freedesktop.systemd1",
1050 "/org/freedesktop/systemd1",
1051 "org.freedesktop.systemd1.Manager",
1057 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1061 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1065 printf("digraph systemd {\n");
1067 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1068 r = graph_one(bus, &u, patterns);
1075 log_info(" Color legend: black = Requires\n"
1076 " dark blue = Requisite\n"
1077 " dark grey = Wants\n"
1078 " red = Conflicts\n"
1079 " green = After\n");
1082 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1083 "-- 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 return bus_log_parse_error(r);
1119 fputs(text, stdout);
1123 static int set_log_level(sd_bus *bus, char **args) {
1124 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1130 if (strv_length(args) != 1) {
1131 log_error("This command expects one argument only.");
1135 r = sd_bus_set_property(
1137 "org.freedesktop.systemd1",
1138 "/org/freedesktop/systemd1",
1139 "org.freedesktop.systemd1.Manager",
1145 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1152 static int help(void) {
1154 pager_open_if_enabled();
1156 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1157 "Process systemd profiling information.\n\n"
1158 " -h --help Show this help\n"
1159 " --version Show package version\n"
1160 " --system Connect to system manager\n"
1161 " --user Connect to user manager\n"
1162 " -H --host=[USER@]HOST Operate on remote host\n"
1163 " -M --machine=CONTAINER Operate on local container\n"
1164 " --order When generating a dependency graph, show only order\n"
1165 " --require When generating a dependency graph, show only requirement\n"
1166 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1167 " When generating a dependency graph, filter only origins\n"
1168 " or destinations, respectively\n"
1169 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1170 " services, which finished TIMESPAN earlier, than the\n"
1171 " latest in the branch. The unit of TIMESPAN is seconds\n"
1172 " unless specified with a different unit, i.e. 50ms\n"
1173 " --no-pager Do not pipe output into a pager\n\n"
1175 " time Print time spent in the kernel before reaching userspace\n"
1176 " blame Print list of running units ordered by time to init\n"
1177 " critical-chain Print a tree of the time critical chain of units\n"
1178 " plot Output SVG graphic showing service initialization\n"
1179 " dot Output dependency graph in dot(1) format\n"
1180 " set-log-level LEVEL Set logging threshold for systemd\n"
1181 " dump Output state serialization of service manager\n",
1182 program_invocation_short_name);
1184 /* When updating this list, including descriptions, apply
1185 * changes to shell-completion/bash/systemd and
1186 * shell-completion/systemd-zsh-completion.zsh too. */
1191 static int parse_argv(int argc, char *argv[]) {
1193 ARG_VERSION = 0x100,
1198 ARG_DOT_FROM_PATTERN,
1204 static const struct option options[] = {
1205 { "help", no_argument, NULL, 'h' },
1206 { "version", no_argument, NULL, ARG_VERSION },
1207 { "order", no_argument, NULL, ARG_ORDER },
1208 { "require", no_argument, NULL, ARG_REQUIRE },
1209 { "user", no_argument, NULL, ARG_USER },
1210 { "system", no_argument, NULL, ARG_SYSTEM },
1211 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1212 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1213 { "fuzz", required_argument, NULL, ARG_FUZZ },
1214 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1223 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1231 puts(PACKAGE_STRING);
1232 puts(SYSTEMD_FEATURES);
1244 arg_dot = DEP_ORDER;
1248 arg_dot = DEP_REQUIRE;
1251 case ARG_DOT_FROM_PATTERN:
1252 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1257 case ARG_DOT_TO_PATTERN:
1258 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1264 r = parse_sec(optarg, &arg_fuzz);
1270 arg_no_pager = true;
1274 arg_transport = BUS_TRANSPORT_REMOTE;
1279 arg_transport = BUS_TRANSPORT_CONTAINER;
1287 assert_not_reached("Unhandled option");
1294 int main(int argc, char *argv[]) {
1295 _cleanup_bus_unref_ sd_bus *bus = NULL;
1298 setlocale(LC_ALL, "");
1299 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1300 log_parse_environment();
1303 r = parse_argv(argc, argv);
1307 r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1309 log_error("Failed to create bus connection: %s", strerror(-r));
1313 if (!argv[optind] || streq(argv[optind], "time"))
1314 r = analyze_time(bus);
1315 else if (streq(argv[optind], "blame"))
1316 r = analyze_blame(bus);
1317 else if (streq(argv[optind], "critical-chain"))
1318 r = analyze_critical_chain(bus, argv+optind+1);
1319 else if (streq(argv[optind], "plot"))
1320 r = analyze_plot(bus);
1321 else if (streq(argv[optind], "dot"))
1322 r = dot(bus, argv+optind+1);
1323 else if (streq(argv[optind], "dump"))
1324 r = dump(bus, argv+optind+1);
1325 else if (streq(argv[optind], "set-log-level"))
1326 r = set_log_level(bus, argv+optind+1);
1328 log_error("Unknown operation '%s'.", argv[optind]);
1333 strv_free(arg_dot_from_patterns);
1334 strv_free(arg_dot_to_patterns);
1336 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;