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>
31 #include "dbus-common.h"
37 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
40 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
42 #define svg(...) printf(__VA_ARGS__)
44 #define svg_bar(class, x1, x2, y) \
45 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
47 SCALE_X * (x1), SCALE_Y * (y), \
48 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
50 #define svg_text(b, x, y, format, ...) \
52 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
53 svg(format, ## __VA_ARGS__); \
57 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
68 usec_t kernel_done_time;
70 usec_t userspace_time;
82 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
83 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
84 DBusMessageIter iter, sub;
87 r = bus_method_call_with_reply(
89 "org.freedesktop.systemd1",
91 "org.freedesktop.DBus.Properties",
95 DBUS_TYPE_STRING, &interface,
96 DBUS_TYPE_STRING, &property,
101 if (!dbus_message_iter_init(reply, &iter) ||
102 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
103 log_error("Failed to parse reply.");
107 dbus_message_iter_recurse(&iter, &sub);
109 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
110 log_error("Failed to parse reply.");
114 dbus_message_iter_get_basic(&sub, val);
119 static int compare_unit_time(const void *a, const void *b) {
120 return compare(((struct unit_times *)b)->time,
121 ((struct unit_times *)a)->time);
124 static int compare_unit_start(const void *a, const void *b) {
125 return compare(((struct unit_times *)a)->ixt,
126 ((struct unit_times *)b)->ixt);
129 static int get_os_name(char **_n) {
133 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
144 static void free_unit_times(struct unit_times *t, unsigned n) {
145 struct unit_times *p;
147 for (p = t; p < t + n; p++)
153 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
154 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
155 DBusMessageIter iter, sub;
156 int r, c = 0, n_units = 0;
157 struct unit_times *unit_times = NULL;
159 r = bus_method_call_with_reply(
161 "org.freedesktop.systemd1",
162 "/org/freedesktop/systemd1",
163 "org.freedesktop.systemd1.Manager",
171 if (!dbus_message_iter_init(reply, &iter) ||
172 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
173 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
174 log_error("Failed to parse reply.");
179 for (dbus_message_iter_recurse(&iter, &sub);
180 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
181 dbus_message_iter_next(&sub)) {
183 struct unit_times *t;
185 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
186 log_error("Failed to parse reply.");
192 struct unit_times *w;
194 n_units = MAX(2*c, 16);
195 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
207 r = bus_parse_unit_info(&sub, &u);
211 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
213 if (bus_get_uint64_property(bus, u.unit_path,
214 "org.freedesktop.systemd1.Unit",
215 "InactiveExitTimestampMonotonic",
217 bus_get_uint64_property(bus, u.unit_path,
218 "org.freedesktop.systemd1.Unit",
219 "ActiveEnterTimestampMonotonic",
221 bus_get_uint64_property(bus, u.unit_path,
222 "org.freedesktop.systemd1.Unit",
223 "ActiveExitTimestampMonotonic",
225 bus_get_uint64_property(bus, u.unit_path,
226 "org.freedesktop.systemd1.Unit",
227 "InactiveEnterTimestampMonotonic",
233 if (t->aet >= t->ixt)
234 t->time = t->aet - t->ixt;
235 else if (t->iet >= t->ixt)
236 t->time = t->iet - t->ixt;
243 t->name = strdup(u.id);
244 if (t->name == NULL) {
255 free_unit_times(unit_times, (unsigned) c);
259 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
260 static struct boot_times times;
261 static bool cached = false;
266 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
268 if (bus_get_uint64_property(bus,
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "FirmwareTimestampMonotonic",
272 ×.firmware_time) < 0 ||
273 bus_get_uint64_property(bus,
274 "/org/freedesktop/systemd1",
275 "org.freedesktop.systemd1.Manager",
276 "LoaderTimestampMonotonic",
277 ×.loader_time) < 0 ||
278 bus_get_uint64_property(bus,
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
282 ×.kernel_time) < 0 ||
283 bus_get_uint64_property(bus,
284 "/org/freedesktop/systemd1",
285 "org.freedesktop.systemd1.Manager",
286 "InitRDTimestampMonotonic",
287 ×.initrd_time) < 0 ||
288 bus_get_uint64_property(bus,
289 "/org/freedesktop/systemd1",
290 "org.freedesktop.systemd1.Manager",
291 "UserspaceTimestampMonotonic",
292 ×.userspace_time) < 0 ||
293 bus_get_uint64_property(bus,
294 "/org/freedesktop/systemd1",
295 "org.freedesktop.systemd1.Manager",
296 "FinishTimestampMonotonic",
297 ×.finish_time) < 0)
300 if (times.finish_time <= 0) {
301 log_error("Bootup is not yet finished. Please try again later.");
305 if (times.initrd_time)
306 times.kernel_done_time = times.initrd_time;
308 times.kernel_done_time = times.userspace_time;
317 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
318 char ts[FORMAT_TIMESPAN_MAX];
319 struct boot_times *t;
320 static char buf[4096];
325 r = acquire_boot_times(bus, &t);
332 size = strpcpyf(&ptr, size, "Startup finished in ");
333 if (t->firmware_time)
334 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time));
336 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time));
338 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time));
339 if (t->initrd_time > 0)
340 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time));
342 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
343 if (t->kernel_time > 0)
344 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time));
346 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
356 static void svg_graph_box(double height, double begin, double end) {
359 /* outside box, fill */
360 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
361 SCALE_X * (end - begin), SCALE_Y * height);
363 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
364 /* lines for each second */
365 if (i % 5000000 == 0)
366 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
367 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
368 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
369 else if (i % 1000000 == 0)
370 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
371 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
372 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
374 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
375 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
379 static int analyze_plot(DBusConnection *bus) {
380 struct unit_times *times;
381 struct boot_times *boot;
385 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
386 struct unit_times *u;
388 n = acquire_boot_times(bus, &boot);
392 n = pretty_boot_time(bus, &pretty_times);
396 get_os_name(&osname);
397 assert_se(uname(&name) >= 0);
399 n = acquire_time_data(bus, ×);
403 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
405 width = SCALE_X * (boot->firmware_time + boot->finish_time);
409 if (boot->firmware_time > boot->loader_time)
411 if (boot->loader_time) {
416 if (boot->initrd_time)
418 if (boot->kernel_time)
421 for (u = times; u < times + n; u++) {
424 if (u->ixt < boot->userspace_time ||
425 u->ixt > boot->finish_time) {
430 len = ((boot->firmware_time + u->ixt) * SCALE_X)
431 + (10.0 * strlen(u->name));
435 if (u->iet > u->ixt && u->iet <= boot->finish_time
436 && u->aet == 0 && u->axt == 0)
437 u->aet = u->axt = u->iet;
438 if (u->aet < u->ixt || u->aet > boot->finish_time)
439 u->aet = boot->finish_time;
440 if (u->axt < u->aet || u->aet > boot->finish_time)
441 u->axt = boot->finish_time;
442 if (u->iet < u->axt || u->iet > boot->finish_time)
443 u->iet = boot->finish_time;
447 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
448 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
449 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
451 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
452 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
453 80.0 + width, 150.0 + (m * SCALE_Y));
455 /* write some basic info as a comment, including some help */
456 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
457 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
458 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
459 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
460 "<!-- point your browser to this file. -->\n\n"
461 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
464 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
465 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
466 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
467 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
468 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
469 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
470 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
471 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
472 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
473 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
474 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
475 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
477 " line.sec5 { stroke-width: 2; }\n"
478 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
479 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
480 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
481 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
482 " text.sec { font-size: 8; }\n"
483 " ]]>\n </style>\n</defs>\n\n");
485 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
486 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
487 isempty(osname) ? "Linux" : osname,
488 name.nodename, name.release, name.version, name.machine);
489 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
490 120.0 + (m *SCALE_Y));
492 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
493 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
495 if (boot->firmware_time) {
496 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
497 svg_text(true, -(double) boot->firmware_time, y, "firmware");
500 if (boot->loader_time) {
501 svg_bar("loader", -(double) boot->loader_time, 0, y);
502 svg_text(true, -(double) boot->loader_time, y, "loader");
505 if (boot->kernel_time) {
506 svg_bar("kernel", 0, boot->kernel_done_time, y);
507 svg_text(true, 0, y, "kernel");
510 if (boot->initrd_time) {
511 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
512 svg_text(true, boot->initrd_time, y, "initrd");
515 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
516 svg_text("left", boot->userspace_time, y, "userspace");
519 for (u = times; u < times + n; u++) {
520 char ts[FORMAT_TIMESPAN_MAX];
525 svg_bar("activating", u->ixt, u->aet, y);
526 svg_bar("active", u->aet, u->axt, y);
527 svg_bar("deactivating", u->axt, u->iet, y);
529 if (u->ixt * SCALE_X > width * 2 / 3)
530 svg_text(false, u->ixt, y, u->time? "%s (%s)" : "%s", u->name, format_timespan(ts, sizeof(ts), u->time));
532 svg_text(true, u->ixt, y, u->time? "%s (%s)" : "%s", u->name, format_timespan(ts, sizeof(ts), u->time));
539 free_unit_times(times, (unsigned) n);
544 static int analyze_blame(DBusConnection *bus) {
545 struct unit_times *times;
549 n = acquire_time_data(bus, ×);
553 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
555 for (i = 0; i < (unsigned) n; i++) {
556 char ts[FORMAT_TIMESPAN_MAX];
558 if (times[i].time > 0)
559 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time), times[i].name);
562 free_unit_times(times, (unsigned) n);
566 static int analyze_time(DBusConnection *bus) {
567 _cleanup_free_ char *buf = NULL;
570 r = pretty_boot_time(bus, &buf);
578 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
580 static const char * const colors[] = {
581 "Requires", "[color=\"black\"]",
582 "RequiresOverridable", "[color=\"black\"]",
583 "Requisite", "[color=\"darkblue\"]",
584 "RequisiteOverridable", "[color=\"darkblue\"]",
585 "Wants", "[color=\"grey66\"]",
586 "Conflicts", "[color=\"red\"]",
587 "ConflictedBy", "[color=\"red\"]",
588 "After", "[color=\"green\"]"
591 const char *c = NULL;
598 for (i = 0; i < ELEMENTSOF(colors); i += 2)
599 if (streq(colors[i], prop)) {
607 if (arg_dot != DEP_ALL)
608 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
611 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
612 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
615 dbus_message_iter_recurse(iter, &sub);
617 for (dbus_message_iter_recurse(iter, &sub);
618 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
619 dbus_message_iter_next(&sub)) {
622 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
623 dbus_message_iter_get_basic(&sub, &s);
624 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
631 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
632 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
633 const char *interface = "org.freedesktop.systemd1.Unit";
635 DBusMessageIter iter, sub, sub2, sub3;
640 r = bus_method_call_with_reply(
642 "org.freedesktop.systemd1",
644 "org.freedesktop.DBus.Properties",
648 DBUS_TYPE_STRING, &interface,
653 if (!dbus_message_iter_init(reply, &iter) ||
654 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
655 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
656 log_error("Failed to parse reply.");
660 for (dbus_message_iter_recurse(&iter, &sub);
661 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
662 dbus_message_iter_next(&sub)) {
665 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
666 dbus_message_iter_recurse(&sub, &sub2);
668 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
669 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
670 log_error("Failed to parse reply.");
674 dbus_message_iter_recurse(&sub2, &sub3);
675 r = graph_one_property(u->id, prop, &sub3);
683 static int dot(DBusConnection *bus) {
684 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
685 DBusMessageIter iter, sub;
688 r = bus_method_call_with_reply(
690 "org.freedesktop.systemd1",
691 "/org/freedesktop/systemd1",
692 "org.freedesktop.systemd1.Manager",
700 if (!dbus_message_iter_init(reply, &iter) ||
701 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
702 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
703 log_error("Failed to parse reply.");
707 printf("digraph systemd {\n");
709 for (dbus_message_iter_recurse(&iter, &sub);
710 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
711 dbus_message_iter_next(&sub)) {
714 r = bus_parse_unit_info(&sub, &u);
718 r = graph_one(bus, &u);
725 log_info(" Color legend: black = Requires\n"
726 " dark blue = Requisite\n"
727 " dark grey = Wants\n"
732 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
733 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
738 static void analyze_help(void)
740 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
741 "Process systemd profiling information\n\n"
742 " -h --help Show this help\n"
743 " --version Show package version\n"
744 " --system Connect to system manager\n"
745 " --user Connect to user service manager\n"
746 " --order When generating a dependency graph, show only order\n"
747 " --require When generating a dependency graph, show only requirement\n\n"
749 " time Print time spent in the kernel before reaching userspace\n"
750 " blame Print list of running units ordered by time to init\n"
751 " plot Output SVG graphic showing service initialization\n"
752 " dot Dump dependency graph (in dot(1) format)\n\n",
753 program_invocation_short_name);
756 static int parse_argv(int argc, char *argv[])
766 static const struct option options[] = {
767 { "help", no_argument, NULL, 'h' },
768 { "version", no_argument, NULL, ARG_VERSION },
769 { "order", no_argument, NULL, ARG_ORDER },
770 { "require", no_argument, NULL, ARG_REQUIRE },
771 { "user", no_argument, NULL, ARG_USER },
772 { "system", no_argument, NULL, ARG_SYSTEM },
780 switch (getopt_long(argc, argv, "h", options, NULL)) {
787 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
791 arg_scope = UNIT_FILE_USER;
795 arg_scope = UNIT_FILE_SYSTEM;
803 arg_dot = DEP_REQUIRE;
813 assert_not_reached("Unhandled option");
818 int main(int argc, char *argv[]) {
820 DBusConnection *bus = NULL;
822 setlocale(LC_ALL, "");
823 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
824 log_parse_environment();
827 r = parse_argv(argc, argv);
833 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
837 if (!argv[optind] || streq(argv[optind], "time"))
838 r = analyze_time(bus);
839 else if (streq(argv[optind], "blame"))
840 r = analyze_blame(bus);
841 else if (streq(argv[optind], "plot"))
842 r = analyze_plot(bus);
843 else if (streq(argv[optind], "dot"))
846 log_error("Unknown operation '%s'.", argv[optind]);
848 dbus_connection_unref(bus);
850 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;