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) {
241 free(unit_times[c].name);
246 static struct boot_times *acquire_boot_times(DBusConnection *bus)
248 static struct boot_times times;
249 static bool cached = false;
253 if (bus_get_uint64_property(bus,
254 "/org/freedesktop/systemd1",
255 "org.freedesktop.systemd1.Manager",
256 "FirmwareTimestampMonotonic",
257 ×.firmware_time) < 0 ||
258 bus_get_uint64_property(bus,
259 "/org/freedesktop/systemd1",
260 "org.freedesktop.systemd1.Manager",
261 "LoaderTimestampMonotonic",
262 ×.loader_time) < 0 ||
263 bus_get_uint64_property(bus,
264 "/org/freedesktop/systemd1",
265 "org.freedesktop.systemd1.Manager",
267 ×.kernel_time) < 0 ||
268 bus_get_uint64_property(bus,
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "InitRDTimestampMonotonic",
272 ×.initrd_time) < 0 ||
273 bus_get_uint64_property(bus,
274 "/org/freedesktop/systemd1",
275 "org.freedesktop.systemd1.Manager",
276 "UserspaceTimestampMonotonic",
277 ×.userspace_time) < 0 ||
278 bus_get_uint64_property(bus,
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
281 "FinishTimestampMonotonic",
282 ×.finish_time) < 0)
285 if (!times.finish_time) {
286 log_error("Bootup is not yet finished. Please try again later.");
290 times.firmware_time /= 1000;
291 times.loader_time /= 1000;
292 times.initrd_time /= 1000;
293 times.userspace_time /= 1000;
294 times.finish_time /= 1000;
296 if (times.initrd_time)
297 times.kernel_done_time = times.initrd_time;
299 times.kernel_done_time = times.userspace_time;
305 static char *pretty_boot_time(DBusConnection *bus)
307 struct boot_times *t;
309 static char buf[4096];
312 t = acquire_boot_times(bus);
316 size = strpcpyf(&ptr, size, "Startup finished in ");
317 if (t->firmware_time)
318 size = strpcpyf(&ptr, size, "%llums (firmware) + ", (unsigned long long)(t->firmware_time - t->loader_time));
320 size = strpcpyf(&ptr, size, "%llums (loader) + ", (unsigned long long)t->loader_time);
322 size = strpcpyf(&ptr, size, "%llums (kernel) + ", (unsigned long long)t->kernel_done_time);
323 if (t->initrd_time > 0)
324 size = strpcpyf(&ptr, size, "%llums (initrd) + ", (unsigned long long)(t->userspace_time - t->initrd_time));
326 size = strpcpyf(&ptr, size, "%llums (userspace) ", (unsigned long long)(t->finish_time - t->userspace_time));
327 if (t->kernel_time > 0)
328 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->firmware_time + t->finish_time));
330 size = strpcpyf(&ptr, size, "= %llums", (unsigned long long)(t->finish_time - t->userspace_time));
335 static void svg_graph_box(int height, int64_t begin, int64_t end)
337 /* outside box, fill */
338 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
339 scale_x * (end - begin), scale_y * height);
341 for (int i = (begin / 100) * 100; i <= end; i+=100) {
342 /* lines for each second */
344 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
345 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
346 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
347 else if (i % 1000 == 0)
348 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
349 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
350 scale_x * i, scale_x * i, scale_y * height, scale_x * i, -5.0, 0.001 * i);
352 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
353 scale_x * i, scale_x * i, scale_y * height);
357 static int analyze_plot(DBusConnection *bus)
359 struct unit_times *times;
360 struct boot_times *boot;
367 boot = acquire_boot_times(bus);
370 pretty_times = pretty_boot_time(bus);
374 osname = get_os_name();
378 log_error("Cannot get system name: %m");
382 n = acquire_time_data(bus, ×);
386 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
388 width = scale_x * (boot->firmware_time + boot->finish_time);
392 if (boot->firmware_time > boot->loader_time)
394 if (boot->loader_time) {
399 if (boot->initrd_time)
401 if (boot->kernel_time)
404 for (struct unit_times *u = times; u < times + n; u++) {
406 if (u->ixt < boot->userspace_time ||
407 u->ixt > boot->finish_time) {
412 len = ((boot->firmware_time + u->ixt) * scale_x)
413 + (10.0 * strlen(u->name));
417 if (u->iet > u->ixt && u->iet <= boot->finish_time
418 && u->aet == 0 && u->axt == 0)
419 u->aet = u->axt = u->iet;
420 if (u->aet < u->ixt || u->aet > boot->finish_time)
421 u->aet = boot->finish_time;
422 if (u->axt < u->aet || u->aet > boot->finish_time)
423 u->axt = boot->finish_time;
424 if (u->iet < u->axt || u->iet > boot->finish_time)
425 u->iet = boot->finish_time;
429 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
430 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
431 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
433 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
434 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
435 80.0 + width, 150.0 + (m * scale_y));
437 /* write some basic info as a comment, including some help */
438 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
439 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
440 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
441 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
442 "<!-- point your browser to this file. -->\n\n"
443 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
446 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
447 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
448 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
449 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
450 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
451 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
452 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
453 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
454 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
455 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
456 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
457 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
459 " line.sec5 { stroke-width: 2; }\n"
460 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
461 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
462 " text.sec { font-size: 8; }\n"
463 " ]]>\n </style>\n</defs>\n\n");
465 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
466 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
467 isempty(osname)? "Linux" : osname,
468 name.nodename, name.release, name.version, name.machine);
469 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
470 120.0 + (m *scale_y));
472 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (scale_x * boot->firmware_time));
473 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
475 if (boot->firmware_time) {
476 svg_bar("firmware", -(int64_t) boot->firmware_time, -(int64_t) boot->loader_time, y);
477 svg_text(-(int64_t) boot->firmware_time, y, "firmware");
480 if (boot->loader_time) {
481 svg_bar("loader", -(int64_t) boot->loader_time, 0, y);
482 svg_text(-(int64_t) boot->loader_time, y, "loader");
485 if (boot->kernel_time) {
486 svg_bar("kernel", 0, boot->kernel_done_time, y);
487 svg_text(0, y, "kernel");
490 if (boot->initrd_time) {
491 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
492 svg_text(boot->initrd_time, y, "initrd");
495 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
496 svg_text(boot->userspace_time, y, "userspace");
499 for (struct unit_times *u = times; u < times + n; u++) {
502 svg_bar("activating", u->ixt, u->aet, y);
503 svg_bar("active", u->aet, u->axt, y);
504 svg_bar("deactivating", u->axt, u->iet, y);
505 svg_text(u->ixt, y, u->time? "%s (%llums)" : "%s", u->name, (unsigned long long)u->time);
514 static int analyze_blame(DBusConnection *bus)
516 struct unit_times *times;
517 int n = acquire_time_data(bus, ×);
521 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
523 for (int i = 0; i < n; i++) {
525 printf("%6llums %s\n", (unsigned long long)times[i].time, times[i].name);
530 static int analyze_time(DBusConnection *bus)
533 buf = pretty_boot_time(bus);
536 if (puts(buf) == EOF)
541 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
543 static const char * const colors[] = {
544 "Requires", "[color=\"black\"]",
545 "RequiresOverridable", "[color=\"black\"]",
546 "Requisite", "[color=\"darkblue\"]",
547 "RequisiteOverridable", "[color=\"darkblue\"]",
548 "Wants", "[color=\"grey66\"]",
549 "Conflicts", "[color=\"red\"]",
550 "ConflictedBy", "[color=\"red\"]",
551 "After", "[color=\"green\"]"
554 const char *c = NULL;
561 for (i = 0; i < ELEMENTSOF(colors); i += 2)
562 if (streq(colors[i], prop)) {
570 if (arg_dot != DEP_ALL)
571 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
574 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
575 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
578 dbus_message_iter_recurse(iter, &sub);
580 for (dbus_message_iter_recurse(iter, &sub);
581 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
582 dbus_message_iter_next(&sub)) {
585 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
586 dbus_message_iter_get_basic(&sub, &s);
587 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
594 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
595 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
596 const char *interface = "org.freedesktop.systemd1.Unit";
598 DBusMessageIter iter, sub, sub2, sub3;
603 r = bus_method_call_with_reply(
605 "org.freedesktop.systemd1",
607 "org.freedesktop.DBus.Properties",
611 DBUS_TYPE_STRING, &interface,
616 if (!dbus_message_iter_init(reply, &iter) ||
617 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
618 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
619 log_error("Failed to parse reply.");
623 for (dbus_message_iter_recurse(&iter, &sub);
624 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
625 dbus_message_iter_next(&sub)) {
628 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
629 dbus_message_iter_recurse(&sub, &sub2);
631 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
632 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
633 log_error("Failed to parse reply.");
637 dbus_message_iter_recurse(&sub2, &sub3);
638 r = graph_one_property(u->id, prop, &sub3);
646 static int dot(DBusConnection *bus) {
647 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
648 DBusMessageIter iter, sub;
651 r = bus_method_call_with_reply(
653 "org.freedesktop.systemd1",
654 "/org/freedesktop/systemd1",
655 "org.freedesktop.systemd1.Manager",
663 if (!dbus_message_iter_init(reply, &iter) ||
664 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
665 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
666 log_error("Failed to parse reply.");
670 printf("digraph systemd {\n");
672 for (dbus_message_iter_recurse(&iter, &sub);
673 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
674 dbus_message_iter_next(&sub)) {
677 r = bus_parse_unit_info(&sub, &u);
681 r = graph_one(bus, &u);
688 log_info(" Color legend: black = Requires\n"
689 " dark blue = Requisite\n"
690 " dark grey = Wants\n"
695 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
696 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
701 static void analyze_help(void)
703 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
704 "Process systemd profiling information\n\n"
705 " -h --help Show this help\n"
706 " --version Show package version\n"
707 " --system Connect to system manager\n"
708 " --user Connect to user service manager\n"
709 " --order When generating a dependency graph, show only order\n"
710 " --require When generating a dependency graph, show only requirement\n\n"
712 " time Print time spent in the kernel before reaching userspace\n"
713 " blame Print list of running units ordered by time to init\n"
714 " plot Output SVG graphic showing service initialization\n"
715 " dot Dump dependency graph (in dot(1) format)\n\n",
716 program_invocation_short_name);
719 static int parse_argv(int argc, char *argv[])
729 static const struct option options[] = {
730 { "help", no_argument, NULL, 'h' },
731 { "version", no_argument, NULL, ARG_VERSION },
732 { "order", no_argument, NULL, ARG_ORDER },
733 { "require", no_argument, NULL, ARG_REQUIRE },
734 { "user", no_argument, NULL, ARG_USER },
735 { "system", no_argument, NULL, ARG_SYSTEM },
743 switch (getopt_long(argc, argv, "h", options, NULL)) {
748 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
751 arg_scope = UNIT_FILE_USER;
754 arg_scope = UNIT_FILE_SYSTEM;
760 arg_dot = DEP_REQUIRE;
767 assert_not_reached("Unhandled option");
772 int main(int argc, char *argv[]) {
774 DBusConnection *bus = NULL;
776 setlocale(LC_ALL, "");
777 log_parse_environment();
780 r = parse_argv(argc, argv);
786 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
790 if (!argv[optind] || streq(argv[optind], "time"))
791 r = analyze_time(bus);
792 else if (streq(argv[optind], "blame"))
793 r = analyze_blame(bus);
794 else if (streq(argv[optind], "plot"))
795 r = analyze_plot(bus);
796 else if (streq(argv[optind], "dot"))
799 log_error("Unknown operation '%s'.", argv[optind]);
801 dbus_connection_unref(bus);