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))
37 #define svg(...) printf(__VA_ARGS__)
38 #define svg_bar(class, x1, x2, y) \
39 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
41 scale_x * (x1), scale_y * (y), \
42 scale_x * ((x2) - (x1)), scale_y - 1.0)
43 #define svg_text(x, y, format, ...) do {\
44 svg(" <text x=\"%.03f\" y=\"%.03f\">", scale_x * (x) + 5.0, scale_y * (y) + 14.0); \
45 svg(format, ## __VA_ARGS__); \
49 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
56 double scale_x = 0.1; // pixels per ms
57 double scale_y = 20.0;
60 uint64_t firmware_time;
63 uint64_t kernel_done_time;
65 uint64_t userspace_time;
77 static int bus_get_uint64_property (DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val)
79 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
81 DBusMessageIter iter, sub;
83 r = bus_method_call_with_reply (
85 "org.freedesktop.systemd1",
87 "org.freedesktop.DBus.Properties",
91 DBUS_TYPE_STRING, &interface,
92 DBUS_TYPE_STRING, &property,
97 if (!dbus_message_iter_init(reply, &iter) ||
98 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
99 log_error("Failed to parse reply.");
103 dbus_message_iter_recurse(&iter, &sub);
105 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
106 log_error("Failed to parse reply.");
110 dbus_message_iter_get_basic(&sub, val);
115 static int compare_unit_time(const void *a, const void *b)
117 return compare(((struct unit_times *)b)->time,
118 ((struct unit_times *)a)->time);
121 static int compare_unit_start(const void *a, const void *b)
123 return compare(((struct unit_times *)a)->ixt,
124 ((struct unit_times *)b)->ixt);
127 static char *get_os_name(void)
131 parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
135 static int acquire_time_data(DBusConnection *bus, struct unit_times **out)
137 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
138 DBusMessageIter iter, sub;
139 int r, c = 0, n_units = 0;
140 struct unit_times *unit_times = NULL;
142 r = bus_method_call_with_reply (
144 "org.freedesktop.systemd1",
145 "/org/freedesktop/systemd1",
146 "org.freedesktop.systemd1.Manager",
154 if (!dbus_message_iter_init(reply, &iter) ||
155 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
156 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
157 log_error("Failed to parse reply.");
162 for (dbus_message_iter_recurse(&iter, &sub);
163 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
164 dbus_message_iter_next(&sub)) {
166 struct unit_times *t;
168 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
169 log_error("Failed to parse reply.");
175 struct unit_times *w;
177 n_units = MAX(2*c, 16);
178 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
190 r = bus_parse_unit_info(&sub, &u);
194 if (bus_get_uint64_property(bus, u.unit_path,
195 "org.freedesktop.systemd1.Unit",
196 "InactiveExitTimestampMonotonic",
198 bus_get_uint64_property(bus, u.unit_path,
199 "org.freedesktop.systemd1.Unit",
200 "ActiveEnterTimestampMonotonic",
202 bus_get_uint64_property(bus, u.unit_path,
203 "org.freedesktop.systemd1.Unit",
204 "ActiveExitTimestampMonotonic",
206 bus_get_uint64_property(bus, u.unit_path,
207 "org.freedesktop.systemd1.Unit",
208 "InactiveEnterTimestampMonotonic",
219 if (t->aet >= t->ixt)
220 t->time = t->aet - t->ixt;
221 else if (t->iet >= t->ixt)
222 t->time = t->iet - t->ixt;
229 t->name = strdup(u.id);
230 if (t->name == NULL) {
242 free(unit_times[c].name);
248 static struct boot_times *acquire_boot_times(DBusConnection *bus)
250 static struct boot_times times;
251 static bool cached = false;
255 if (bus_get_uint64_property(bus,
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
258 "FirmwareTimestampMonotonic",
259 ×.firmware_time) < 0 ||
260 bus_get_uint64_property(bus,
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
263 "LoaderTimestampMonotonic",
264 ×.loader_time) < 0 ||
265 bus_get_uint64_property(bus,
266 "/org/freedesktop/systemd1",
267 "org.freedesktop.systemd1.Manager",
269 ×.kernel_time) < 0 ||
270 bus_get_uint64_property(bus,
271 "/org/freedesktop/systemd1",
272 "org.freedesktop.systemd1.Manager",
273 "InitRDTimestampMonotonic",
274 ×.initrd_time) < 0 ||
275 bus_get_uint64_property(bus,
276 "/org/freedesktop/systemd1",
277 "org.freedesktop.systemd1.Manager",
278 "UserspaceTimestampMonotonic",
279 ×.userspace_time) < 0 ||
280 bus_get_uint64_property(bus,
281 "/org/freedesktop/systemd1",
282 "org.freedesktop.systemd1.Manager",
283 "FinishTimestampMonotonic",
284 ×.finish_time) < 0)
287 if (!times.finish_time) {
288 log_error("Bootup is not yet finished. Please try again later.");
292 times.firmware_time /= 1000;
293 times.loader_time /= 1000;
294 times.initrd_time /= 1000;
295 times.userspace_time /= 1000;
296 times.finish_time /= 1000;
298 if (times.initrd_time)
299 times.kernel_done_time = times.initrd_time;
301 times.kernel_done_time = times.userspace_time;
307 static char *pretty_boot_time(DBusConnection *bus)
309 struct boot_times *t;
311 static char buf[4096];
314 t = acquire_boot_times(bus);
318 size = strpcpyf(&ptr, size, "Startup finished in ");
319 if (t->firmware_time)
320 size = strpcpyf(&ptr, size, "%llums (firmware) + ", (unsigned long long)(t->firmware_time - t->loader_time));
322 size = strpcpyf(&ptr, size, "%llums (loader) + ", (unsigned long long)t->loader_time);
324 size = strpcpyf(&ptr, size, "%llums (kernel) + ", (unsigned long long)t->kernel_done_time);
325 if (t->initrd_time > 0)
326 size = strpcpyf(&ptr, size, "%llums (initrd) + ", (unsigned long long)(t->userspace_time - t->initrd_time));
328 size = strpcpyf(&ptr, size, "%llums (userspace) ", (unsigned long long)(t->finish_time - t->userspace_time));
329 if (t->kernel_time > 0)
330 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->firmware_time + t->finish_time));
332 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->finish_time - t->userspace_time));
337 static void svg_graph_box(int height, int64_t begin, int64_t end)
339 /* outside box, fill */
340 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
341 scale_x * (end - begin), scale_y * height);
343 for (int i = (begin / 100) * 100; i <= end; i+=100) {
344 /* lines for each second */
346 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
347 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
348 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
349 else if (i % 1000 == 0)
350 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
351 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
352 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
354 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
355 scale_x * i, scale_x * i, scale_y * height);
359 static int analyze_plot(DBusConnection *bus)
361 struct unit_times *times;
362 struct boot_times *boot;
369 boot = acquire_boot_times(bus);
372 pretty_times = pretty_boot_time(bus);
376 osname = get_os_name();
380 log_error("Cannot get system name: %m");
384 n = acquire_time_data(bus, ×);
388 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
390 width = scale_x * (boot->firmware_time + boot->finish_time);
394 if (boot->firmware_time > boot->loader_time)
396 if (boot->loader_time) {
401 if (boot->initrd_time)
403 if (boot->kernel_time)
406 for (struct unit_times *u = times; u < times + n; u++) {
408 if (u->ixt < boot->userspace_time ||
409 u->ixt > boot->finish_time) {
414 len = ((boot->firmware_time + u->ixt) * scale_x)
415 + (10.0 * strlen(u->name));
419 if (u->iet > u->ixt && u->iet <= boot->finish_time
420 && u->aet == 0 && u->axt == 0)
421 u->aet = u->axt = u->iet;
422 if (u->aet < u->ixt || u->aet > boot->finish_time)
423 u->aet = boot->finish_time;
424 if (u->axt < u->aet || u->aet > boot->finish_time)
425 u->axt = boot->finish_time;
426 if (u->iet < u->axt || u->iet > boot->finish_time)
427 u->iet = boot->finish_time;
431 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
432 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
433 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
435 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
436 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
437 80.0 + width, 150.0 + (m * scale_y));
439 /* write some basic info as a comment, including some help */
440 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
441 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
442 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
443 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
444 "<!-- point your browser to this file. -->\n\n"
445 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
448 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
449 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
450 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
451 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
452 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
453 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
454 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
455 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
456 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
457 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
458 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
459 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
461 " line.sec5 { stroke-width: 2; }\n"
462 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
463 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
464 " text.sec { font-size: 8; }\n"
465 " ]]>\n </style>\n</defs>\n\n");
467 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
468 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
469 isempty(osname)? "Linux" : osname,
470 name.nodename, name.release, name.version, name.machine);
471 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
472 120.0 + (m *scale_y));
474 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (scale_x * boot->firmware_time));
475 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
477 if (boot->firmware_time) {
478 svg_bar("firmware", -(int64_t) boot->firmware_time, -(int64_t) boot->loader_time, y);
479 svg_text(-(int64_t) boot->firmware_time, y, "firmware");
482 if (boot->loader_time) {
483 svg_bar("loader", -(int64_t) boot->loader_time, 0, y);
484 svg_text(-(int64_t) boot->loader_time, y, "loader");
487 if (boot->kernel_time) {
488 svg_bar("kernel", 0, boot->kernel_done_time, y);
489 svg_text(0, y, "kernel");
492 if (boot->initrd_time) {
493 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
494 svg_text(boot->initrd_time, y, "initrd");
497 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
498 svg_text(boot->userspace_time, y, "userspace");
501 for (struct unit_times *u = times; u < times + n; u++) {
504 svg_bar("activating", u->ixt, u->aet, y);
505 svg_bar("active", u->aet, u->axt, y);
506 svg_bar("deactivating", u->axt, u->iet, y);
507 svg_text(u->ixt, y, u->time? "%s (%llums)" : "%s", u->name, (unsigned long long)u->time);
516 static int analyze_blame(DBusConnection *bus)
518 struct unit_times *times;
519 int n = acquire_time_data(bus, ×);
523 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
525 for (int i = 0; i < n; i++) {
527 printf("%6llums %s\n", (unsigned long long)times[i].time, times[i].name);
532 static int analyze_time(DBusConnection *bus)
535 buf = pretty_boot_time(bus);
538 if (puts(buf) == EOF)
543 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
545 static const char * const colors[] = {
546 "Requires", "[color=\"black\"]",
547 "RequiresOverridable", "[color=\"black\"]",
548 "Requisite", "[color=\"darkblue\"]",
549 "RequisiteOverridable", "[color=\"darkblue\"]",
550 "Wants", "[color=\"grey66\"]",
551 "Conflicts", "[color=\"red\"]",
552 "ConflictedBy", "[color=\"red\"]",
553 "After", "[color=\"green\"]"
556 const char *c = NULL;
563 for (i = 0; i < ELEMENTSOF(colors); i += 2)
564 if (streq(colors[i], prop)) {
572 if (arg_dot != DEP_ALL)
573 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
576 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
577 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
580 dbus_message_iter_recurse(iter, &sub);
582 for (dbus_message_iter_recurse(iter, &sub);
583 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
584 dbus_message_iter_next(&sub)) {
587 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
588 dbus_message_iter_get_basic(&sub, &s);
589 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
596 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
597 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
598 const char *interface = "org.freedesktop.systemd1.Unit";
600 DBusMessageIter iter, sub, sub2, sub3;
605 r = bus_method_call_with_reply(
607 "org.freedesktop.systemd1",
609 "org.freedesktop.DBus.Properties",
613 DBUS_TYPE_STRING, &interface,
618 if (!dbus_message_iter_init(reply, &iter) ||
619 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
620 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
621 log_error("Failed to parse reply.");
625 for (dbus_message_iter_recurse(&iter, &sub);
626 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
627 dbus_message_iter_next(&sub)) {
630 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
631 dbus_message_iter_recurse(&sub, &sub2);
633 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
634 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
635 log_error("Failed to parse reply.");
639 dbus_message_iter_recurse(&sub2, &sub3);
640 r = graph_one_property(u->id, prop, &sub3);
648 static int dot(DBusConnection *bus) {
649 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
650 DBusMessageIter iter, sub;
653 r = bus_method_call_with_reply(
655 "org.freedesktop.systemd1",
656 "/org/freedesktop/systemd1",
657 "org.freedesktop.systemd1.Manager",
665 if (!dbus_message_iter_init(reply, &iter) ||
666 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
667 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
668 log_error("Failed to parse reply.");
672 printf("digraph systemd {\n");
674 for (dbus_message_iter_recurse(&iter, &sub);
675 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
676 dbus_message_iter_next(&sub)) {
679 r = bus_parse_unit_info(&sub, &u);
683 r = graph_one(bus, &u);
690 log_info(" Color legend: black = Requires\n"
691 " dark blue = Requisite\n"
692 " dark grey = Wants\n"
697 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
698 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
703 static void analyze_help(void)
705 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
706 "Process systemd profiling information\n\n"
707 " -h --help Show this help\n"
708 " --version Show package version\n"
709 " --system Connect to system manager\n"
710 " --user Connect to user service manager\n"
711 " --order When generating a dependency graph, show only order\n"
712 " --require When generating a dependency graph, show only requirement\n\n"
714 " time Print time spent in the kernel before reaching userspace\n"
715 " blame Print list of running units ordered by time to init\n"
716 " plot Output SVG graphic showing service initialization\n"
717 " dot Dump dependency graph (in dot(1) format)\n\n",
718 program_invocation_short_name);
721 static int parse_argv(int argc, char *argv[])
731 static const struct option options[] = {
732 { "help", no_argument, NULL, 'h' },
733 { "version", no_argument, NULL, ARG_VERSION },
734 { "order", no_argument, NULL, ARG_ORDER },
735 { "require", no_argument, NULL, ARG_REQUIRE },
736 { "user", no_argument, NULL, ARG_USER },
737 { "system", no_argument, NULL, ARG_SYSTEM },
745 switch (getopt_long(argc, argv, "h", options, NULL)) {
750 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
753 arg_scope = UNIT_FILE_USER;
756 arg_scope = UNIT_FILE_SYSTEM;
762 arg_dot = DEP_REQUIRE;
769 assert_not_reached("Unhandled option");
774 int main(int argc, char *argv[]) {
776 DBusConnection *bus = NULL;
778 setlocale(LC_ALL, "");
779 log_parse_environment();
782 r = parse_argv(argc, argv);
788 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
792 if (!argv[optind] || streq(argv[optind], "time"))
793 r = analyze_time(bus);
794 else if (streq(argv[optind], "blame"))
795 r = analyze_blame(bus);
796 else if (streq(argv[optind], "plot"))
797 r = analyze_plot(bus);
798 else if (streq(argv[optind], "dot"))
801 log_error("Unknown operation '%s'.", argv[optind]);
803 dbus_connection_unref(bus);