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"
43 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
46 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
48 #define svg(...) printf(__VA_ARGS__)
50 #define svg_bar(class, x1, x2, y) \
51 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
53 SCALE_X * (x1), SCALE_Y * (y), \
54 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
56 #define svg_text(b, x, y, format, ...) \
58 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
59 svg(format, ## __VA_ARGS__); \
63 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
69 static char** arg_dot_from_patterns = NULL;
70 static char** arg_dot_to_patterns = NULL;
71 static usec_t arg_fuzz = 0;
72 static bool arg_no_pager = false;
78 usec_t kernel_done_time;
80 usec_t userspace_time;
82 usec_t generators_start_time;
83 usec_t generators_finish_time;
84 usec_t unitsload_start_time;
85 usec_t unitsload_finish_time;
97 static void pager_open_if_enabled(void) {
105 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
106 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
107 DBusMessageIter iter, sub;
110 r = bus_method_call_with_reply(
112 "org.freedesktop.systemd1",
114 "org.freedesktop.DBus.Properties",
118 DBUS_TYPE_STRING, &interface,
119 DBUS_TYPE_STRING, &property,
124 if (!dbus_message_iter_init(reply, &iter) ||
125 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
126 log_error("Failed to parse reply.");
130 dbus_message_iter_recurse(&iter, &sub);
132 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
133 log_error("Failed to parse reply.");
137 dbus_message_iter_get_basic(&sub, val);
142 static int compare_unit_time(const void *a, const void *b) {
143 return compare(((struct unit_times *)b)->time,
144 ((struct unit_times *)a)->time);
147 static int compare_unit_start(const void *a, const void *b) {
148 return compare(((struct unit_times *)a)->ixt,
149 ((struct unit_times *)b)->ixt);
152 static int get_os_name(char **_n) {
156 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
167 static void free_unit_times(struct unit_times *t, unsigned n) {
168 struct unit_times *p;
170 for (p = t; p < t + n; p++)
176 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
177 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
178 DBusMessageIter iter, sub;
179 int r, c = 0, n_units = 0;
180 struct unit_times *unit_times = NULL;
182 r = bus_method_call_with_reply(
184 "org.freedesktop.systemd1",
185 "/org/freedesktop/systemd1",
186 "org.freedesktop.systemd1.Manager",
194 if (!dbus_message_iter_init(reply, &iter) ||
195 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
196 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
197 log_error("Failed to parse reply.");
202 for (dbus_message_iter_recurse(&iter, &sub);
203 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
204 dbus_message_iter_next(&sub)) {
206 struct unit_times *t;
208 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
209 log_error("Failed to parse reply.");
215 struct unit_times *w;
217 n_units = MAX(2*c, 16);
218 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
230 r = bus_parse_unit_info(&sub, &u);
234 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
236 if (bus_get_uint64_property(bus, u.unit_path,
237 "org.freedesktop.systemd1.Unit",
238 "InactiveExitTimestampMonotonic",
240 bus_get_uint64_property(bus, u.unit_path,
241 "org.freedesktop.systemd1.Unit",
242 "ActiveEnterTimestampMonotonic",
244 bus_get_uint64_property(bus, u.unit_path,
245 "org.freedesktop.systemd1.Unit",
246 "ActiveExitTimestampMonotonic",
248 bus_get_uint64_property(bus, u.unit_path,
249 "org.freedesktop.systemd1.Unit",
250 "InactiveEnterTimestampMonotonic",
256 if (t->aet >= t->ixt)
257 t->time = t->aet - t->ixt;
258 else if (t->iet >= t->ixt)
259 t->time = t->iet - t->ixt;
266 t->name = strdup(u.id);
267 if (t->name == NULL) {
278 free_unit_times(unit_times, (unsigned) c);
282 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
283 static struct boot_times times;
284 static bool cached = false;
289 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
291 if (bus_get_uint64_property(bus,
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
294 "FirmwareTimestampMonotonic",
295 ×.firmware_time) < 0 ||
296 bus_get_uint64_property(bus,
297 "/org/freedesktop/systemd1",
298 "org.freedesktop.systemd1.Manager",
299 "LoaderTimestampMonotonic",
300 ×.loader_time) < 0 ||
301 bus_get_uint64_property(bus,
302 "/org/freedesktop/systemd1",
303 "org.freedesktop.systemd1.Manager",
305 ×.kernel_time) < 0 ||
306 bus_get_uint64_property(bus,
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
309 "InitRDTimestampMonotonic",
310 ×.initrd_time) < 0 ||
311 bus_get_uint64_property(bus,
312 "/org/freedesktop/systemd1",
313 "org.freedesktop.systemd1.Manager",
314 "UserspaceTimestampMonotonic",
315 ×.userspace_time) < 0 ||
316 bus_get_uint64_property(bus,
317 "/org/freedesktop/systemd1",
318 "org.freedesktop.systemd1.Manager",
319 "FinishTimestampMonotonic",
320 ×.finish_time) < 0 ||
321 bus_get_uint64_property(bus,
322 "/org/freedesktop/systemd1",
323 "org.freedesktop.systemd1.Manager",
324 "GeneratorsStartTimestampMonotonic",
325 ×.generators_start_time) < 0 ||
326 bus_get_uint64_property(bus,
327 "/org/freedesktop/systemd1",
328 "org.freedesktop.systemd1.Manager",
329 "GeneratorsFinishTimestampMonotonic",
330 ×.generators_finish_time) < 0 ||
331 bus_get_uint64_property(bus,
332 "/org/freedesktop/systemd1",
333 "org.freedesktop.systemd1.Manager",
334 "UnitsLoadStartTimestampMonotonic",
335 ×.unitsload_start_time) < 0 ||
336 bus_get_uint64_property(bus,
337 "/org/freedesktop/systemd1",
338 "org.freedesktop.systemd1.Manager",
339 "UnitsLoadFinishTimestampMonotonic",
340 ×.unitsload_finish_time) < 0)
343 if (times.finish_time <= 0) {
344 log_error("Bootup is not yet finished. Please try again later.");
348 if (times.initrd_time)
349 times.kernel_done_time = times.initrd_time;
351 times.kernel_done_time = times.userspace_time;
360 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
361 char ts[FORMAT_TIMESPAN_MAX];
362 struct boot_times *t;
363 static char buf[4096];
368 r = acquire_boot_times(bus, &t);
375 size = strpcpyf(&ptr, size, "Startup finished in ");
376 if (t->firmware_time)
377 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
379 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
381 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
382 if (t->initrd_time > 0)
383 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
385 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
386 if (t->kernel_time > 0)
387 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
389 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
399 static void svg_graph_box(double height, double begin, double end) {
402 /* outside box, fill */
403 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
404 SCALE_X * (end - begin), SCALE_Y * height);
406 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
407 /* lines for each second */
408 if (i % 5000000 == 0)
409 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
410 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
411 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
412 else if (i % 1000000 == 0)
413 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
414 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
415 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
417 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
418 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
422 static int analyze_plot(DBusConnection *bus) {
423 struct unit_times *times;
424 struct boot_times *boot;
428 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
429 struct unit_times *u;
431 n = acquire_boot_times(bus, &boot);
435 n = pretty_boot_time(bus, &pretty_times);
439 get_os_name(&osname);
440 assert_se(uname(&name) >= 0);
442 n = acquire_time_data(bus, ×);
446 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
448 width = SCALE_X * (boot->firmware_time + boot->finish_time);
452 if (boot->firmware_time > boot->loader_time)
454 if (boot->loader_time) {
459 if (boot->initrd_time)
461 if (boot->kernel_time)
464 for (u = times; u < times + n; u++) {
467 if (u->ixt < boot->userspace_time ||
468 u->ixt > boot->finish_time) {
473 len = ((boot->firmware_time + u->ixt) * SCALE_X)
474 + (10.0 * strlen(u->name));
478 if (u->iet > u->ixt && u->iet <= boot->finish_time
479 && u->aet == 0 && u->axt == 0)
480 u->aet = u->axt = u->iet;
481 if (u->aet < u->ixt || u->aet > boot->finish_time)
482 u->aet = boot->finish_time;
483 if (u->axt < u->aet || u->aet > boot->finish_time)
484 u->axt = boot->finish_time;
485 if (u->iet < u->axt || u->iet > boot->finish_time)
486 u->iet = boot->finish_time;
490 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
491 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
492 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
494 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
495 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
496 80.0 + width, 150.0 + (m * SCALE_Y) +
497 5 * SCALE_Y /* legend */);
499 /* write some basic info as a comment, including some help */
500 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
501 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
502 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
503 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
504 "<!-- point your browser to this file. -->\n\n"
505 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
508 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
509 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
510 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
511 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
512 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
513 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
514 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
515 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
516 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
517 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
518 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
519 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
520 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
521 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
523 " line.sec5 { stroke-width: 2; }\n"
524 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
525 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
526 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
527 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
528 " text.sec { font-size: 10px; }\n"
529 " ]]>\n </style>\n</defs>\n\n");
531 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
532 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
533 isempty(osname) ? "Linux" : osname,
534 name.nodename, name.release, name.version, name.machine);
536 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
537 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
539 if (boot->firmware_time) {
540 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
541 svg_text(true, -(double) boot->firmware_time, y, "firmware");
544 if (boot->loader_time) {
545 svg_bar("loader", -(double) boot->loader_time, 0, y);
546 svg_text(true, -(double) boot->loader_time, y, "loader");
549 if (boot->kernel_time) {
550 svg_bar("kernel", 0, boot->kernel_done_time, y);
551 svg_text(true, 0, y, "kernel");
554 if (boot->initrd_time) {
555 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
556 svg_text(true, boot->initrd_time, y, "initrd");
559 svg_bar("active", boot->userspace_time, boot->finish_time, y);
560 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
561 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
562 svg_text("left", boot->userspace_time, y, "systemd");
565 for (u = times; u < times + n; u++) {
566 char ts[FORMAT_TIMESPAN_MAX];
572 svg_bar("activating", u->ixt, u->aet, y);
573 svg_bar("active", u->aet, u->axt, y);
574 svg_bar("deactivating", u->axt, u->iet, y);
576 b = u->ixt * SCALE_X > width * 2 / 3;
578 svg_text(b, u->ixt, y, "%s (%s)",
579 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
581 svg_text(b, u->ixt, y, "%s", u->name);
587 svg_bar("activating", 0, 300000, y);
588 svg_text("right", 400000, y, "Activating");
590 svg_bar("active", 0, 300000, y);
591 svg_text("right", 400000, y, "Active");
593 svg_bar("deactivating", 0, 300000, y);
594 svg_text("right", 400000, y, "Deactivating");
596 svg_bar("generators", 0, 300000, y);
597 svg_text("right", 400000, y, "Generators");
599 svg_bar("unitsload", 0, 300000, y);
600 svg_text("right", 400000, y, "Loading unit files");
607 free_unit_times(times, (unsigned) n);
612 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
613 bool last, struct unit_times *times, struct boot_times *boot) {
615 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
617 for (i = level; i != 0; i--)
618 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
620 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
624 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
625 format_timespan(ts, sizeof(ts), times->ixt - boot->userspace_time, USEC_PER_MSEC),
626 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
627 else if (times->aet > boot->userspace_time)
628 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
631 } else printf("%s", name);
637 static int list_dependencies_get_dependencies(DBusConnection *bus, const char *name, char ***deps) {
638 static const char dependencies[] =
641 _cleanup_free_ char *path;
642 const char *interface = "org.freedesktop.systemd1.Unit";
644 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
645 DBusMessageIter iter, sub, sub2, sub3;
654 path = unit_dbus_path_from_name(name);
660 r = bus_method_call_with_reply(
662 "org.freedesktop.systemd1",
664 "org.freedesktop.DBus.Properties",
668 DBUS_TYPE_STRING, &interface,
673 if (!dbus_message_iter_init(reply, &iter) ||
674 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
675 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
676 log_error("Failed to parse reply.");
681 dbus_message_iter_recurse(&iter, &sub);
683 while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
686 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
687 dbus_message_iter_recurse(&sub, &sub2);
689 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0) {
690 log_error("Failed to parse reply.");
695 if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
696 log_error("Failed to parse reply.");
701 dbus_message_iter_recurse(&sub2, &sub3);
702 dbus_message_iter_next(&sub);
704 if (!nulstr_contains(dependencies, prop))
707 if (dbus_message_iter_get_arg_type(&sub3) == DBUS_TYPE_ARRAY) {
708 if (dbus_message_iter_get_element_type(&sub3) == DBUS_TYPE_STRING) {
709 DBusMessageIter sub4;
710 dbus_message_iter_recurse(&sub3, &sub4);
712 while (dbus_message_iter_get_arg_type(&sub4) != DBUS_TYPE_INVALID) {
715 assert(dbus_message_iter_get_arg_type(&sub4) == DBUS_TYPE_STRING);
716 dbus_message_iter_get_basic(&sub4, &s);
718 r = strv_extend(&ret, s);
724 dbus_message_iter_next(&sub4);
737 static Hashmap *unit_times_hashmap;
739 static int list_dependencies_compare(const void *_a, const void *_b) {
740 const char **a = (const char**) _a, **b = (const char**) _b;
741 usec_t usa = 0, usb = 0;
742 struct unit_times *times;
744 times = hashmap_get(unit_times_hashmap, *a);
747 times = hashmap_get(unit_times_hashmap, *b);
754 static int list_dependencies_one(DBusConnection *bus, const char *name, unsigned int level, char ***units,
755 unsigned int branches) {
756 _cleanup_strv_free_ char **deps = NULL;
759 usec_t service_longest = 0;
761 struct unit_times *times;
762 struct boot_times *boot;
764 if(strv_extend(units, name))
767 r = list_dependencies_get_dependencies(bus, name, &deps);
771 qsort(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
773 r = acquire_boot_times(bus, &boot);
777 STRV_FOREACH(c, deps) {
778 times = hashmap_get(unit_times_hashmap, *c);
781 && times->aet <= boot->finish_time
782 && (times->aet >= service_longest
783 || service_longest == 0)) {
784 service_longest = times->aet;
789 if (service_longest == 0 )
792 STRV_FOREACH(c, deps) {
793 times = hashmap_get(unit_times_hashmap, *c);
794 if (times && times->aet
795 && times->aet <= boot->finish_time
796 && (service_longest - times->aet) <= arg_fuzz) {
804 STRV_FOREACH(c, deps) {
805 times = hashmap_get(unit_times_hashmap, *c);
808 || times->aet > boot->finish_time
809 || service_longest - times->aet > arg_fuzz)
814 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
818 if (strv_contains(*units, *c)) {
819 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
824 r = list_dependencies_one(bus, *c, level + 1, units,
825 (branches << 1) | (to_print ? 1 : 0));
837 static int list_dependencies(DBusConnection *bus, const char *name) {
838 _cleanup_strv_free_ char **units = NULL;
839 char ts[FORMAT_TIMESPAN_MAX];
840 struct unit_times *times;
844 *interface = "org.freedesktop.systemd1.Unit",
846 DBusMessageIter iter, sub;
847 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
848 struct boot_times *boot;
852 path = unit_dbus_path_from_name(name);
856 r = bus_method_call_with_reply (
858 "org.freedesktop.systemd1",
860 "org.freedesktop.DBus.Properties",
864 DBUS_TYPE_STRING, &interface,
865 DBUS_TYPE_STRING, &property,
870 if (!dbus_message_iter_init(reply, &iter) ||
871 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
872 log_error("Failed to parse reply.");
876 dbus_message_iter_recurse(&iter, &sub);
878 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRING) {
879 log_error("Failed to parse reply.");
883 dbus_message_iter_get_basic(&sub, &id);
885 times = hashmap_get(unit_times_hashmap, id);
887 r = acquire_boot_times(bus, &boot);
893 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
894 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
895 else if (times->aet > boot->userspace_time)
896 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
901 return list_dependencies_one(bus, name, 0, &units, 0);
904 static int analyze_critical_chain(DBusConnection *bus, char *names[]) {
905 struct unit_times *times;
910 n = acquire_time_data(bus, ×);
914 h = hashmap_new(string_hash_func, string_compare_func);
918 for (i = 0; i < (unsigned)n; i++) {
919 r = hashmap_put(h, times[i].name, ×[i]);
923 unit_times_hashmap = h;
925 pager_open_if_enabled();
927 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
928 "The time the unit takes to start is printed after the \"+\" character.\n");
930 if (!strv_isempty(names)) {
932 STRV_FOREACH(name, names)
933 list_dependencies(bus, *name);
935 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
938 free_unit_times(times, (unsigned) n);
942 static int analyze_blame(DBusConnection *bus) {
943 struct unit_times *times;
947 n = acquire_time_data(bus, ×);
951 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
953 pager_open_if_enabled();
955 for (i = 0; i < (unsigned) n; i++) {
956 char ts[FORMAT_TIMESPAN_MAX];
958 if (times[i].time > 0)
959 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
962 free_unit_times(times, (unsigned) n);
966 static int analyze_time(DBusConnection *bus) {
967 _cleanup_free_ char *buf = NULL;
970 r = pretty_boot_time(bus, &buf);
978 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
980 static const char * const colors[] = {
981 "Requires", "[color=\"black\"]",
982 "RequiresOverridable", "[color=\"black\"]",
983 "Requisite", "[color=\"darkblue\"]",
984 "RequisiteOverridable", "[color=\"darkblue\"]",
985 "Wants", "[color=\"grey66\"]",
986 "Conflicts", "[color=\"red\"]",
987 "ConflictedBy", "[color=\"red\"]",
988 "After", "[color=\"green\"]"
991 const char *c = NULL;
998 for (i = 0; i < ELEMENTSOF(colors); i += 2)
999 if (streq(colors[i], prop)) {
1007 if (arg_dot != DEP_ALL)
1008 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
1011 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
1012 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
1013 DBusMessageIter sub;
1015 dbus_message_iter_recurse(iter, &sub);
1017 for (dbus_message_iter_recurse(iter, &sub);
1018 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1019 dbus_message_iter_next(&sub)) {
1024 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
1025 dbus_message_iter_get_basic(&sub, &s);
1027 if (!strv_isempty(arg_dot_from_patterns)) {
1028 match_found = false;
1030 STRV_FOREACH(p, arg_dot_from_patterns)
1031 if (fnmatch(*p, name, 0) == 0) {
1040 if (!strv_isempty(arg_dot_to_patterns)) {
1041 match_found = false;
1043 STRV_FOREACH(p, arg_dot_to_patterns)
1044 if (fnmatch(*p, s, 0) == 0) {
1053 if (!strv_isempty(patterns)) {
1054 match_found = false;
1056 STRV_FOREACH(p, patterns)
1057 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
1065 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
1072 static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
1073 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1074 const char *interface = "org.freedesktop.systemd1.Unit";
1076 DBusMessageIter iter, sub, sub2, sub3;
1081 r = bus_method_call_with_reply(
1083 "org.freedesktop.systemd1",
1085 "org.freedesktop.DBus.Properties",
1089 DBUS_TYPE_STRING, &interface,
1094 if (!dbus_message_iter_init(reply, &iter) ||
1095 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1096 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
1097 log_error("Failed to parse reply.");
1101 for (dbus_message_iter_recurse(&iter, &sub);
1102 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1103 dbus_message_iter_next(&sub)) {
1106 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
1107 dbus_message_iter_recurse(&sub, &sub2);
1109 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
1110 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
1111 log_error("Failed to parse reply.");
1115 dbus_message_iter_recurse(&sub2, &sub3);
1116 r = graph_one_property(u->id, prop, &sub3, patterns);
1124 static int dot(DBusConnection *bus, char* patterns[]) {
1125 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
1126 DBusMessageIter iter, sub;
1129 r = bus_method_call_with_reply(
1131 "org.freedesktop.systemd1",
1132 "/org/freedesktop/systemd1",
1133 "org.freedesktop.systemd1.Manager",
1141 if (!dbus_message_iter_init(reply, &iter) ||
1142 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
1143 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
1144 log_error("Failed to parse reply.");
1148 printf("digraph systemd {\n");
1150 for (dbus_message_iter_recurse(&iter, &sub);
1151 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
1152 dbus_message_iter_next(&sub)) {
1155 r = bus_parse_unit_info(&sub, &u);
1159 r = graph_one(bus, &u, patterns);
1166 log_info(" Color legend: black = Requires\n"
1167 " dark blue = Requisite\n"
1168 " dark grey = Wants\n"
1169 " red = Conflicts\n"
1170 " green = After\n");
1173 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1174 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1179 static int dump(DBusConnection *bus, char **args) {
1180 _cleanup_free_ DBusMessage *reply = NULL;
1185 dbus_error_init(&error);
1187 if (!strv_isempty(args)) {
1188 log_error("Too many arguments.");
1192 pager_open_if_enabled();
1194 r = bus_method_call_with_reply(
1196 "org.freedesktop.systemd1",
1197 "/org/freedesktop/systemd1",
1198 "org.freedesktop.systemd1.Manager",
1206 if (!dbus_message_get_args(reply, &error,
1207 DBUS_TYPE_STRING, &text,
1208 DBUS_TYPE_INVALID)) {
1209 log_error("Failed to parse reply: %s", bus_error_message(&error));
1210 dbus_error_free(&error);
1214 fputs(text, stdout);
1218 static int set_log_level(DBusConnection *bus, char **args) {
1219 _cleanup_dbus_error_free_ DBusError error;
1220 _cleanup_dbus_message_unref_ DBusMessage *m = NULL, *reply = NULL;
1221 DBusMessageIter iter, sub;
1222 const char* property = "LogLevel";
1223 const char* interface = "org.freedesktop.systemd1.Manager";
1229 if (strv_length(args) != 1) {
1230 log_error("This command expects one argument only.");
1235 dbus_error_init(&error);
1237 m = dbus_message_new_method_call("org.freedesktop.systemd1",
1238 "/org/freedesktop/systemd1",
1239 "org.freedesktop.DBus.Properties",
1244 dbus_message_iter_init_append(m, &iter);
1246 if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &interface) ||
1247 !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &property) ||
1248 !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, "s", &sub))
1251 if (!dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &value))
1254 if (!dbus_message_iter_close_container(&iter, &sub))
1257 reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &error);
1259 log_error("Failed to issue method call: %s", bus_error_message(&error));
1266 static void analyze_help(void) {
1268 pager_open_if_enabled();
1270 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1271 "Process systemd profiling information\n\n"
1272 " -h --help Show this help\n"
1273 " --version Show package version\n"
1274 " --system Connect to system manager\n"
1275 " --user Connect to user service manager\n"
1276 " --order When generating a dependency graph, show only order\n"
1277 " --require When generating a dependency graph, show only requirement\n"
1278 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1279 " When generating a dependency graph, filter only origins\n"
1280 " or destinations, respectively\n"
1281 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1282 " services, which finished TIMESPAN earlier, than the\n"
1283 " latest in the branch. The unit of TIMESPAN is seconds\n"
1284 " unless specified with a different unit, i.e. 50ms\n"
1285 " --no-pager Do not pipe output into a pager\n\n"
1287 " time Print time spent in the kernel before reaching userspace\n"
1288 " blame Print list of running units ordered by time to init\n"
1289 " critical-chain Print a tree of the time critical chain of units\n"
1290 " plot Output SVG graphic showing service initialization\n"
1291 " dot Output dependency graph in dot(1) format\n"
1292 " set-log-level LEVEL Set logging threshold for systemd\n"
1293 " dump Output state serialization of service manager\n",
1294 program_invocation_short_name);
1296 /* When updating this list, including descriptions, apply
1297 * changes to shell-completion/bash/systemd and
1298 * shell-completion/systemd-zsh-completion.zsh too. */
1301 static int parse_argv(int argc, char *argv[]) {
1305 ARG_VERSION = 0x100,
1310 ARG_DOT_FROM_PATTERN,
1316 static const struct option options[] = {
1317 { "help", no_argument, NULL, 'h' },
1318 { "version", no_argument, NULL, ARG_VERSION },
1319 { "order", no_argument, NULL, ARG_ORDER },
1320 { "require", no_argument, NULL, ARG_REQUIRE },
1321 { "user", no_argument, NULL, ARG_USER },
1322 { "system", no_argument, NULL, ARG_SYSTEM },
1323 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1324 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1325 { "fuzz", required_argument, NULL, ARG_FUZZ },
1326 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1327 { NULL, 0, NULL, 0 }
1334 switch (getopt_long(argc, argv, "h", options, NULL)) {
1341 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1345 arg_scope = UNIT_FILE_USER;
1349 arg_scope = UNIT_FILE_SYSTEM;
1353 arg_dot = DEP_ORDER;
1357 arg_dot = DEP_REQUIRE;
1360 case ARG_DOT_FROM_PATTERN:
1361 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1366 case ARG_DOT_TO_PATTERN:
1367 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1373 r = parse_sec(optarg, &arg_fuzz);
1379 arg_no_pager = true;
1389 assert_not_reached("Unhandled option");
1394 int main(int argc, char *argv[]) {
1396 DBusConnection *bus = NULL;
1398 setlocale(LC_ALL, "");
1399 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1400 log_parse_environment();
1403 r = parse_argv(argc, argv);
1407 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
1413 if (!argv[optind] || streq(argv[optind], "time"))
1414 r = analyze_time(bus);
1415 else if (streq(argv[optind], "blame"))
1416 r = analyze_blame(bus);
1417 else if (streq(argv[optind], "critical-chain"))
1418 r = analyze_critical_chain(bus, argv+optind+1);
1419 else if (streq(argv[optind], "plot"))
1420 r = analyze_plot(bus);
1421 else if (streq(argv[optind], "dot"))
1422 r = dot(bus, argv+optind+1);
1423 else if (streq(argv[optind], "dump"))
1424 r = dump(bus, argv+optind+1);
1425 else if (streq(argv[optind], "set-log-level"))
1426 r = set_log_level(bus, argv+optind+1);
1428 log_error("Unknown operation '%s'.", argv[optind]);
1430 dbus_connection_unref(bus);
1435 strv_free(arg_dot_from_patterns);
1436 strv_free(arg_dot_to_patterns);
1438 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;