1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
26 #include <sys/utsname.h>
30 #include "dbus-common.h"
36 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
38 #define svg(...) printf(__VA_ARGS__)
40 #define svg_bar(class, x1, x2, y) \
41 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
43 scale_x * (x1), scale_y * (y), \
44 scale_x * ((x2) - (x1)), scale_y - 1.0)
46 #define svg_text(b, x, y, format, ...) \
48 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", scale_x * (x) + (b ? 5.0 : -5.0), scale_y * (y) + 14.0); \
49 svg(format, ## __VA_ARGS__); \
53 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
60 static double scale_x = 0.1 / 1000.0; /* pixels per us */
61 static double scale_y = 20.0;
67 usec_t kernel_done_time;
69 usec_t userspace_time;
81 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
82 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
83 DBusMessageIter iter, sub;
86 r = bus_method_call_with_reply(
88 "org.freedesktop.systemd1",
90 "org.freedesktop.DBus.Properties",
94 DBUS_TYPE_STRING, &interface,
95 DBUS_TYPE_STRING, &property,
100 if (!dbus_message_iter_init(reply, &iter) ||
101 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
102 log_error("Failed to parse reply.");
106 dbus_message_iter_recurse(&iter, &sub);
108 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
109 log_error("Failed to parse reply.");
113 dbus_message_iter_get_basic(&sub, val);
118 static int compare_unit_time(const void *a, const void *b) {
119 return compare(((struct unit_times *)b)->time,
120 ((struct unit_times *)a)->time);
123 static int compare_unit_start(const void *a, const void *b) {
124 return compare(((struct unit_times *)a)->ixt,
125 ((struct unit_times *)b)->ixt);
128 static int get_os_name(char **_n) {
132 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
143 static void free_unit_times(struct unit_times *t, unsigned n) {
144 struct unit_times *p;
146 for (p = t; p < t + n; p++)
152 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
153 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
154 DBusMessageIter iter, sub;
155 int r, c = 0, n_units = 0;
156 struct unit_times *unit_times = NULL;
158 r = bus_method_call_with_reply(
160 "org.freedesktop.systemd1",
161 "/org/freedesktop/systemd1",
162 "org.freedesktop.systemd1.Manager",
170 if (!dbus_message_iter_init(reply, &iter) ||
171 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
172 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
173 log_error("Failed to parse reply.");
178 for (dbus_message_iter_recurse(&iter, &sub);
179 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
180 dbus_message_iter_next(&sub)) {
182 struct unit_times *t;
184 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
185 log_error("Failed to parse reply.");
191 struct unit_times *w;
193 n_units = MAX(2*c, 16);
194 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
206 r = bus_parse_unit_info(&sub, &u);
210 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
212 if (bus_get_uint64_property(bus, u.unit_path,
213 "org.freedesktop.systemd1.Unit",
214 "InactiveExitTimestampMonotonic",
216 bus_get_uint64_property(bus, u.unit_path,
217 "org.freedesktop.systemd1.Unit",
218 "ActiveEnterTimestampMonotonic",
220 bus_get_uint64_property(bus, u.unit_path,
221 "org.freedesktop.systemd1.Unit",
222 "ActiveExitTimestampMonotonic",
224 bus_get_uint64_property(bus, u.unit_path,
225 "org.freedesktop.systemd1.Unit",
226 "InactiveEnterTimestampMonotonic",
232 if (t->aet >= t->ixt)
233 t->time = t->aet - t->ixt;
234 else if (t->iet >= t->ixt)
235 t->time = t->iet - t->ixt;
242 t->name = strdup(u.id);
243 if (t->name == NULL) {
254 free_unit_times(unit_times, (unsigned) c);
258 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
259 static struct boot_times times;
260 static bool cached = false;
265 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
267 if (bus_get_uint64_property(bus,
268 "/org/freedesktop/systemd1",
269 "org.freedesktop.systemd1.Manager",
270 "FirmwareTimestampMonotonic",
271 ×.firmware_time) < 0 ||
272 bus_get_uint64_property(bus,
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "LoaderTimestampMonotonic",
276 ×.loader_time) < 0 ||
277 bus_get_uint64_property(bus,
278 "/org/freedesktop/systemd1",
279 "org.freedesktop.systemd1.Manager",
281 ×.kernel_time) < 0 ||
282 bus_get_uint64_property(bus,
283 "/org/freedesktop/systemd1",
284 "org.freedesktop.systemd1.Manager",
285 "InitRDTimestampMonotonic",
286 ×.initrd_time) < 0 ||
287 bus_get_uint64_property(bus,
288 "/org/freedesktop/systemd1",
289 "org.freedesktop.systemd1.Manager",
290 "UserspaceTimestampMonotonic",
291 ×.userspace_time) < 0 ||
292 bus_get_uint64_property(bus,
293 "/org/freedesktop/systemd1",
294 "org.freedesktop.systemd1.Manager",
295 "FinishTimestampMonotonic",
296 ×.finish_time) < 0)
299 if (times.finish_time <= 0) {
300 log_error("Bootup is not yet finished. Please try again later.");
304 if (times.initrd_time)
305 times.kernel_done_time = times.initrd_time;
307 times.kernel_done_time = times.userspace_time;
316 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
317 char ts[FORMAT_TIMESPAN_MAX];
318 struct boot_times *t;
319 static char buf[4096];
324 r = acquire_boot_times(bus, &t);
331 size = strpcpyf(&ptr, size, "Startup finished in ");
332 if (t->firmware_time)
333 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time));
335 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time));
337 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time));
338 if (t->initrd_time > 0)
339 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time));
341 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
342 if (t->kernel_time > 0)
343 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time));
345 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time));
355 static void svg_graph_box(double height, double begin, double end) {
358 /* outside box, fill */
359 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
360 scale_x * (end - begin), scale_y * height);
362 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
363 /* lines for each second */
364 if (i % 5000000 == 0)
365 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
366 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
367 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.000001 * i);
368 else if (i % 1000000 == 0)
369 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
370 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
371 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.000001 * i);
373 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
374 scale_x * i, scale_x * i, scale_y * height);
378 static int analyze_plot(DBusConnection *bus) {
379 struct unit_times *times;
380 struct boot_times *boot;
384 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
385 struct unit_times *u;
387 n = acquire_boot_times(bus, &boot);
391 n = pretty_boot_time(bus, &pretty_times);
395 get_os_name(&osname);
396 assert_se(uname(&name) >= 0);
398 n = acquire_time_data(bus, ×);
402 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
404 width = scale_x * (boot->firmware_time + boot->finish_time);
408 if (boot->firmware_time > boot->loader_time)
410 if (boot->loader_time) {
415 if (boot->initrd_time)
417 if (boot->kernel_time)
420 for (u = times; u < times + n; u++) {
423 if (u->ixt < boot->userspace_time ||
424 u->ixt > boot->finish_time) {
429 len = ((boot->firmware_time + u->ixt) * scale_x)
430 + (10.0 * strlen(u->name));
434 if (u->iet > u->ixt && u->iet <= boot->finish_time
435 && u->aet == 0 && u->axt == 0)
436 u->aet = u->axt = u->iet;
437 if (u->aet < u->ixt || u->aet > boot->finish_time)
438 u->aet = boot->finish_time;
439 if (u->axt < u->aet || u->aet > boot->finish_time)
440 u->axt = boot->finish_time;
441 if (u->iet < u->axt || u->iet > boot->finish_time)
442 u->iet = boot->finish_time;
446 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
447 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
448 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
450 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
451 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
452 80.0 + width, 150.0 + (m * scale_y));
454 /* write some basic info as a comment, including some help */
455 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
456 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
457 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
458 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
459 "<!-- point your browser to this file. -->\n\n"
460 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
463 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
464 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
465 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
466 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
467 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
468 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
469 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
470 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
471 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
472 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
473 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
474 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
476 " line.sec5 { stroke-width: 2; }\n"
477 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
478 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
479 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
480 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
481 " text.sec { font-size: 8; }\n"
482 " ]]>\n </style>\n</defs>\n\n");
484 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
485 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
486 isempty(osname) ? "Linux" : osname,
487 name.nodename, name.release, name.version, name.machine);
488 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
489 120.0 + (m *scale_y));
491 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (scale_x * boot->firmware_time));
492 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
494 if (boot->firmware_time) {
495 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
496 svg_text(true, -(double) boot->firmware_time, y, "firmware");
499 if (boot->loader_time) {
500 svg_bar("loader", -(double) boot->loader_time, 0, y);
501 svg_text(true, -(double) boot->loader_time, y, "loader");
504 if (boot->kernel_time) {
505 svg_bar("kernel", 0, boot->kernel_done_time, y);
506 svg_text(true, 0, y, "kernel");
509 if (boot->initrd_time) {
510 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
511 svg_text(true, boot->initrd_time, y, "initrd");
514 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
515 svg_text("left", boot->userspace_time, y, "userspace");
518 for (u = times; u < times + n; u++) {
519 char ts[FORMAT_TIMESPAN_MAX];
524 svg_bar("activating", u->ixt, u->aet, y);
525 svg_bar("active", u->aet, u->axt, y);
526 svg_bar("deactivating", u->axt, u->iet, y);
528 if (u->ixt * scale_x > width * 2 / 3)
529 svg_text(false, u->ixt, y, u->time? "%s (%s)" : "%s", u->name, format_timespan(ts, sizeof(ts), u->time));
531 svg_text(true, u->ixt, y, u->time? "%s (%s)" : "%s", u->name, format_timespan(ts, sizeof(ts), u->time));
538 free_unit_times(times, (unsigned) n);
543 static int analyze_blame(DBusConnection *bus) {
544 struct unit_times *times;
548 n = acquire_time_data(bus, ×);
552 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
554 for (i = 0; i < (unsigned) n; i++) {
555 char ts[FORMAT_TIMESPAN_MAX];
557 if (times[i].time > 0)
558 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time), times[i].name);
561 free_unit_times(times, (unsigned) n);
565 static int analyze_time(DBusConnection *bus) {
566 _cleanup_free_ char *buf = NULL;
569 r = pretty_boot_time(bus, &buf);
577 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
579 static const char * const colors[] = {
580 "Requires", "[color=\"black\"]",
581 "RequiresOverridable", "[color=\"black\"]",
582 "Requisite", "[color=\"darkblue\"]",
583 "RequisiteOverridable", "[color=\"darkblue\"]",
584 "Wants", "[color=\"grey66\"]",
585 "Conflicts", "[color=\"red\"]",
586 "ConflictedBy", "[color=\"red\"]",
587 "After", "[color=\"green\"]"
590 const char *c = NULL;
597 for (i = 0; i < ELEMENTSOF(colors); i += 2)
598 if (streq(colors[i], prop)) {
606 if (arg_dot != DEP_ALL)
607 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
610 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
611 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
614 dbus_message_iter_recurse(iter, &sub);
616 for (dbus_message_iter_recurse(iter, &sub);
617 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
618 dbus_message_iter_next(&sub)) {
621 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
622 dbus_message_iter_get_basic(&sub, &s);
623 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
630 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
631 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
632 const char *interface = "org.freedesktop.systemd1.Unit";
634 DBusMessageIter iter, sub, sub2, sub3;
639 r = bus_method_call_with_reply(
641 "org.freedesktop.systemd1",
643 "org.freedesktop.DBus.Properties",
647 DBUS_TYPE_STRING, &interface,
652 if (!dbus_message_iter_init(reply, &iter) ||
653 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
654 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
655 log_error("Failed to parse reply.");
659 for (dbus_message_iter_recurse(&iter, &sub);
660 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
661 dbus_message_iter_next(&sub)) {
664 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
665 dbus_message_iter_recurse(&sub, &sub2);
667 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
668 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
669 log_error("Failed to parse reply.");
673 dbus_message_iter_recurse(&sub2, &sub3);
674 r = graph_one_property(u->id, prop, &sub3);
682 static int dot(DBusConnection *bus) {
683 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
684 DBusMessageIter iter, sub;
687 r = bus_method_call_with_reply(
689 "org.freedesktop.systemd1",
690 "/org/freedesktop/systemd1",
691 "org.freedesktop.systemd1.Manager",
699 if (!dbus_message_iter_init(reply, &iter) ||
700 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
701 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
702 log_error("Failed to parse reply.");
706 printf("digraph systemd {\n");
708 for (dbus_message_iter_recurse(&iter, &sub);
709 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
710 dbus_message_iter_next(&sub)) {
713 r = bus_parse_unit_info(&sub, &u);
717 r = graph_one(bus, &u);
724 log_info(" Color legend: black = Requires\n"
725 " dark blue = Requisite\n"
726 " dark grey = Wants\n"
731 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
732 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
737 static void analyze_help(void)
739 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
740 "Process systemd profiling information\n\n"
741 " -h --help Show this help\n"
742 " --version Show package version\n"
743 " --system Connect to system manager\n"
744 " --user Connect to user service manager\n"
745 " --order When generating a dependency graph, show only order\n"
746 " --require When generating a dependency graph, show only requirement\n\n"
748 " time Print time spent in the kernel before reaching userspace\n"
749 " blame Print list of running units ordered by time to init\n"
750 " plot Output SVG graphic showing service initialization\n"
751 " dot Dump dependency graph (in dot(1) format)\n\n",
752 program_invocation_short_name);
755 static int parse_argv(int argc, char *argv[])
765 static const struct option options[] = {
766 { "help", no_argument, NULL, 'h' },
767 { "version", no_argument, NULL, ARG_VERSION },
768 { "order", no_argument, NULL, ARG_ORDER },
769 { "require", no_argument, NULL, ARG_REQUIRE },
770 { "user", no_argument, NULL, ARG_USER },
771 { "system", no_argument, NULL, ARG_SYSTEM },
779 switch (getopt_long(argc, argv, "h", options, NULL)) {
786 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
790 arg_scope = UNIT_FILE_USER;
794 arg_scope = UNIT_FILE_SYSTEM;
802 arg_dot = DEP_REQUIRE;
812 assert_not_reached("Unhandled option");
817 int main(int argc, char *argv[]) {
819 DBusConnection *bus = NULL;
821 setlocale(LC_ALL, "");
822 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
823 log_parse_environment();
826 r = parse_argv(argc, argv);
832 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
836 if (!argv[optind] || streq(argv[optind], "time"))
837 r = analyze_time(bus);
838 else if (streq(argv[optind], "blame"))
839 r = analyze_blame(bus);
840 else if (streq(argv[optind], "plot"))
841 r = analyze_plot(bus);
842 else if (streq(argv[optind], "dot"))
845 log_error("Unknown operation '%s'.", argv[optind]);
847 dbus_connection_unref(bus);
849 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;