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"
35 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
36 #define svg(...) printf(__VA_ARGS__)
37 #define svg_bar(class, x1, x2, y) \
38 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
40 scale_x * (x1), scale_y * (y), \
41 scale_x * ((x2) - (x1)), scale_y - 1.0)
42 #define svg_text(x, y, format, ...) do {\
43 svg(" <text x=\"%.03f\" y=\"%.03f\">", scale_x * (x) + 5.0, scale_y * (y) + 14.0); \
44 svg(format, ## __VA_ARGS__); \
48 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
55 double scale_x = 0.1; // pixels per ms
56 double scale_y = 20.0;
59 uint64_t firmware_time;
62 uint64_t kernel_done_time;
64 uint64_t userspace_time;
76 static int bus_get_uint64_property (DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val)
78 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
80 DBusMessageIter iter, sub;
82 r = bus_method_call_with_reply (
84 "org.freedesktop.systemd1",
86 "org.freedesktop.DBus.Properties",
90 DBUS_TYPE_STRING, &interface,
91 DBUS_TYPE_STRING, &property,
96 if (!dbus_message_iter_init(reply, &iter) ||
97 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
98 log_error("Failed to parse reply.");
102 dbus_message_iter_recurse(&iter, &sub);
104 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
105 log_error("Failed to parse reply.");
109 dbus_message_iter_get_basic(&sub, val);
114 static int compare_unit_time(const void *a, const void *b)
116 return compare(((struct unit_times *)b)->time,
117 ((struct unit_times *)a)->time);
120 static int compare_unit_start(const void *a, const void *b)
122 return compare(((struct unit_times *)a)->ixt,
123 ((struct unit_times *)b)->ixt);
126 static char *get_os_name(void)
130 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
134 static int acquire_time_data(DBusConnection *bus, struct unit_times **out)
136 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
137 DBusMessageIter iter, sub;
138 int r, c = 0, n_units = 0;
139 struct unit_times *unit_times = NULL;
141 r = bus_method_call_with_reply (
143 "org.freedesktop.systemd1",
144 "/org/freedesktop/systemd1",
145 "org.freedesktop.systemd1.Manager",
153 if (!dbus_message_iter_init(reply, &iter) ||
154 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
155 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
156 log_error("Failed to parse reply.");
161 for (dbus_message_iter_recurse(&iter, &sub);
162 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
163 dbus_message_iter_next(&sub)) {
165 struct unit_times *t;
167 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
168 log_error("Failed to parse reply.");
174 struct unit_times *w;
176 n_units = MAX(2*c, 16);
177 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
189 r = bus_parse_unit_info(&sub, &u);
193 if (bus_get_uint64_property(bus, u.unit_path,
194 "org.freedesktop.systemd1.Unit",
195 "InactiveExitTimestampMonotonic",
197 bus_get_uint64_property(bus, u.unit_path,
198 "org.freedesktop.systemd1.Unit",
199 "ActiveEnterTimestampMonotonic",
201 bus_get_uint64_property(bus, u.unit_path,
202 "org.freedesktop.systemd1.Unit",
203 "ActiveExitTimestampMonotonic",
205 bus_get_uint64_property(bus, u.unit_path,
206 "org.freedesktop.systemd1.Unit",
207 "InactiveEnterTimestampMonotonic",
218 if (t->aet >= t->ixt)
219 t->time = t->aet - t->ixt;
220 else if (t->iet >= t->ixt)
221 t->time = t->iet - t->ixt;
228 t->name = strdup(u.id);
229 if (t->name == NULL) {
240 free(unit_times[c].name);
245 static struct boot_times *acquire_boot_times(DBusConnection *bus)
247 static struct boot_times times;
248 static bool cached = false;
252 if (bus_get_uint64_property(bus,
253 "/org/freedesktop/systemd1",
254 "org.freedesktop.systemd1.Manager",
255 "FirmwareTimestampMonotonic",
256 ×.firmware_time) < 0 ||
257 bus_get_uint64_property(bus,
258 "/org/freedesktop/systemd1",
259 "org.freedesktop.systemd1.Manager",
260 "LoaderTimestampMonotonic",
261 ×.loader_time) < 0 ||
262 bus_get_uint64_property(bus,
263 "/org/freedesktop/systemd1",
264 "org.freedesktop.systemd1.Manager",
266 ×.kernel_time) < 0 ||
267 bus_get_uint64_property(bus,
268 "/org/freedesktop/systemd1",
269 "org.freedesktop.systemd1.Manager",
270 "InitRDTimestampMonotonic",
271 ×.initrd_time) < 0 ||
272 bus_get_uint64_property(bus,
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "UserspaceTimestampMonotonic",
276 ×.userspace_time) < 0 ||
277 bus_get_uint64_property(bus,
278 "/org/freedesktop/systemd1",
279 "org.freedesktop.systemd1.Manager",
280 "FinishTimestampMonotonic",
281 ×.finish_time) < 0)
284 if (!times.finish_time) {
285 log_error("Bootup is not yet finished. Please try again later.");
289 times.firmware_time /= 1000;
290 times.loader_time /= 1000;
291 times.initrd_time /= 1000;
292 times.userspace_time /= 1000;
293 times.finish_time /= 1000;
295 if (times.initrd_time)
296 times.kernel_done_time = times.initrd_time;
298 times.kernel_done_time = times.userspace_time;
304 static char *pretty_boot_time(DBusConnection *bus)
306 struct boot_times *t;
308 static char buf[4096];
311 t = acquire_boot_times(bus);
315 size = strpcpyf(&ptr, size, "Startup finished in ");
316 if (t->firmware_time)
317 size = strpcpyf(&ptr, size, "%llums (firmware) + ", (unsigned long long)(t->firmware_time - t->loader_time));
319 size = strpcpyf(&ptr, size, "%llums (loader) + ", (unsigned long long)t->loader_time);
321 size = strpcpyf(&ptr, size, "%llums (kernel) + ", (unsigned long long)t->kernel_done_time);
322 if (t->initrd_time > 0)
323 size = strpcpyf(&ptr, size, "%llums (initrd) + ", (unsigned long long)(t->userspace_time - t->initrd_time));
325 size = strpcpyf(&ptr, size, "%llums (userspace) ", (unsigned long long)(t->finish_time - t->userspace_time));
326 if (t->kernel_time > 0)
327 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->firmware_time + t->finish_time));
329 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->finish_time - t->userspace_time));
334 static void svg_graph_box(int height, int64_t begin, int64_t end)
336 /* outside box, fill */
337 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
338 scale_x * (end - begin), scale_y * height);
340 for (int i = (begin / 100) * 100; i <= end; i+=100) {
341 /* lines for each second */
343 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
344 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
345 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
346 else if (i % 1000 == 0)
347 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
348 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
349 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
351 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
352 scale_x * i, scale_x * i, scale_y * height);
356 static int analyze_plot(DBusConnection *bus)
358 struct unit_times *times;
359 struct boot_times *boot;
366 boot = acquire_boot_times(bus);
369 pretty_times = pretty_boot_time(bus);
373 osname = get_os_name();
377 log_error("Cannot get system name: %m");
381 n = acquire_time_data(bus, ×);
385 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
387 width = scale_x * (boot->firmware_time + boot->finish_time);
391 if (boot->firmware_time > boot->loader_time)
393 if (boot->loader_time) {
398 if (boot->initrd_time)
400 if (boot->kernel_time)
403 for (struct unit_times *u = times; u < times + n; u++) {
405 if (u->ixt < boot->userspace_time ||
406 u->ixt > boot->finish_time) {
411 len = ((boot->firmware_time + u->ixt) * scale_x)
412 + (10.0 * strlen(u->name));
416 if (u->iet > u->ixt && u->iet <= boot->finish_time
417 && u->aet == 0 && u->axt == 0)
418 u->aet = u->axt = u->iet;
419 if (u->aet < u->ixt || u->aet > boot->finish_time)
420 u->aet = boot->finish_time;
421 if (u->axt < u->aet || u->aet > boot->finish_time)
422 u->axt = boot->finish_time;
423 if (u->iet < u->axt || u->iet > boot->finish_time)
424 u->iet = boot->finish_time;
428 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
429 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
430 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
432 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
433 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
434 80.0 + width, 150.0 + (m * scale_y));
436 /* write some basic info as a comment, including some help */
437 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
438 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
439 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
440 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
441 "<!-- point your browser to this file. -->\n\n"
442 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
445 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
446 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
447 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
448 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
449 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
450 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
451 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
452 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
453 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
454 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
455 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
456 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
458 " line.sec5 { stroke-width: 2; }\n"
459 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
460 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
461 " text.sec { font-size: 8; }\n"
462 " ]]>\n </style>\n</defs>\n\n");
464 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
465 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
466 isempty(osname)? "Linux" : osname,
467 name.nodename, name.release, name.version, name.machine);
468 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
469 120.0 + (m *scale_y));
471 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (scale_x * boot->firmware_time));
472 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
474 if (boot->firmware_time) {
475 svg_bar("firmware", -(int64_t) boot->firmware_time, -(int64_t) boot->loader_time, y);
476 svg_text(-(int64_t) boot->firmware_time, y, "firmware");
479 if (boot->loader_time) {
480 svg_bar("loader", -(int64_t) boot->loader_time, 0, y);
481 svg_text(-(int64_t) boot->loader_time, y, "loader");
484 if (boot->kernel_time) {
485 svg_bar("kernel", 0, boot->kernel_done_time, y);
486 svg_text(0, y, "kernel");
489 if (boot->initrd_time) {
490 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
491 svg_text(boot->initrd_time, y, "initrd");
494 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
495 svg_text(boot->userspace_time, y, "userspace");
498 for (struct unit_times *u = times; u < times + n; u++) {
501 svg_bar("activating", u->ixt, u->aet, y);
502 svg_bar("active", u->aet, u->axt, y);
503 svg_bar("deactivating", u->axt, u->iet, y);
504 svg_text(u->ixt, y, u->time? "%s (%llums)" : "%s", u->name, (unsigned long long)u->time);
513 static int analyze_blame(DBusConnection *bus)
515 struct unit_times *times;
516 int n = acquire_time_data(bus, ×);
520 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
522 for (int i = 0; i < n; i++) {
524 printf("%6llums %s\n", (unsigned long long)times[i].time, times[i].name);
529 static int analyze_time(DBusConnection *bus)
532 buf = pretty_boot_time(bus);
535 if (puts(buf) == EOF)
540 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
542 static const char * const colors[] = {
543 "Requires", "[color=\"black\"]",
544 "RequiresOverridable", "[color=\"black\"]",
545 "Requisite", "[color=\"darkblue\"]",
546 "RequisiteOverridable", "[color=\"darkblue\"]",
547 "Wants", "[color=\"grey66\"]",
548 "Conflicts", "[color=\"red\"]",
549 "ConflictedBy", "[color=\"red\"]",
550 "After", "[color=\"green\"]"
553 const char *c = NULL;
560 for (i = 0; i < ELEMENTSOF(colors); i += 2)
561 if (streq(colors[i], prop)) {
569 if (arg_dot != DEP_ALL)
570 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
573 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
574 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
577 dbus_message_iter_recurse(iter, &sub);
579 for (dbus_message_iter_recurse(iter, &sub);
580 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
581 dbus_message_iter_next(&sub)) {
584 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
585 dbus_message_iter_get_basic(&sub, &s);
586 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
593 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
594 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
595 const char *interface = "org.freedesktop.systemd1.Unit";
597 DBusMessageIter iter, sub, sub2, sub3;
602 r = bus_method_call_with_reply(
604 "org.freedesktop.systemd1",
606 "org.freedesktop.DBus.Properties",
610 DBUS_TYPE_STRING, &interface,
615 if (!dbus_message_iter_init(reply, &iter) ||
616 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
617 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
618 log_error("Failed to parse reply.");
622 for (dbus_message_iter_recurse(&iter, &sub);
623 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
624 dbus_message_iter_next(&sub)) {
627 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
628 dbus_message_iter_recurse(&sub, &sub2);
630 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
631 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
632 log_error("Failed to parse reply.");
636 dbus_message_iter_recurse(&sub2, &sub3);
637 r = graph_one_property(u->id, prop, &sub3);
645 static int dot(DBusConnection *bus) {
646 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
647 DBusMessageIter iter, sub;
650 r = bus_method_call_with_reply(
652 "org.freedesktop.systemd1",
653 "/org/freedesktop/systemd1",
654 "org.freedesktop.systemd1.Manager",
662 if (!dbus_message_iter_init(reply, &iter) ||
663 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
664 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
665 log_error("Failed to parse reply.");
669 printf("digraph systemd {\n");
671 for (dbus_message_iter_recurse(&iter, &sub);
672 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
673 dbus_message_iter_next(&sub)) {
676 r = bus_parse_unit_info(&sub, &u);
680 r = graph_one(bus, &u);
687 log_info(" Color legend: black = Requires\n"
688 " dark blue = Requisite\n"
689 " dark grey = Wants\n"
694 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
695 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
700 static void analyze_help(void)
702 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
703 "Process systemd profiling information\n\n"
704 " -h --help Show this help\n"
705 " --version Show package version\n"
706 " --system Connect to system manager\n"
707 " --user Connect to user service manager\n"
708 " --order When generating a dependency graph, show only order\n"
709 " --require When generating a dependency graph, show only requirement\n\n"
711 " time Print time spent in the kernel before reaching userspace\n"
712 " blame Print list of running units ordered by time to init\n"
713 " plot Output SVG graphic showing service initialization\n"
714 " dot Dump dependency graph (in dot(1) format)\n\n",
715 program_invocation_short_name);
718 static int parse_argv(int argc, char *argv[])
728 static const struct option options[] = {
729 { "help", no_argument, NULL, 'h' },
730 { "version", no_argument, NULL, ARG_VERSION },
731 { "order", no_argument, NULL, ARG_ORDER },
732 { "require", no_argument, NULL, ARG_REQUIRE },
733 { "user", no_argument, NULL, ARG_USER },
734 { "system", no_argument, NULL, ARG_SYSTEM },
742 switch (getopt_long(argc, argv, "h", options, NULL)) {
747 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
750 arg_scope = UNIT_FILE_USER;
753 arg_scope = UNIT_FILE_SYSTEM;
759 arg_dot = DEP_REQUIRE;
766 assert_not_reached("Unhandled option");
771 int main(int argc, char *argv[]) {
773 DBusConnection *bus = NULL;
775 setlocale(LC_ALL, "");
776 log_parse_environment();
779 r = parse_argv(argc, argv);
785 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
789 if (!argv[optind] || streq(argv[optind], "time"))
790 r = analyze_time(bus);
791 else if (streq(argv[optind], "blame"))
792 r = analyze_blame(bus);
793 else if (streq(argv[optind], "plot"))
794 r = analyze_plot(bus);
795 else if (streq(argv[optind], "dot"))
798 log_error("Unknown operation '%s'.", argv[optind]);
800 dbus_connection_unref(bus);