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/>.
30 #include "analyze-verify.h"
31 #include "bus-error.h"
39 #include "terminal-util.h"
40 #include "unit-name.h"
43 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
44 #define SCALE_Y (20.0)
46 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
48 #define svg(...) printf(__VA_ARGS__)
50 #define svg_bar(class, x1, x2, y) \
51 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
53 SCALE_X * (x1), SCALE_Y * (y), \
54 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
56 #define svg_text(b, x, y, format, ...) \
58 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
59 svg(format, ## __VA_ARGS__); \
68 static char** arg_dot_from_patterns
= NULL
;
69 static char** arg_dot_to_patterns
= NULL
;
70 static usec_t arg_fuzz
= 0;
71 static bool arg_no_pager
= false;
72 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
73 static char *arg_host
= NULL
;
74 static bool arg_user
= false;
75 static bool arg_man
= true;
81 usec_t kernel_done_time
;
83 usec_t userspace_time
;
85 usec_t security_start_time
;
86 usec_t security_finish_time
;
87 usec_t generators_start_time
;
88 usec_t generators_finish_time
;
89 usec_t unitsload_start_time
;
90 usec_t unitsload_finish_time
;
93 * If we're analyzing the user instance, all timestamps will be offset
94 * by its own start-up timestamp, which may be arbitrarily big.
95 * With "plot", this causes arbitrarily wide output SVG files which almost
96 * completely consist of empty space. Thus we cancel out this offset.
98 * This offset is subtracted from times above by acquire_boot_times(),
99 * but it still needs to be subtracted from unit-specific timestamps
100 * (so it is stored here for reference).
102 usec_t reverse_offset
;
117 char *kernel_release
;
118 char *kernel_version
;
119 char *os_pretty_name
;
120 char *virtualization
;
124 static void pager_open_if_enabled(void) {
132 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
133 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
142 r
= sd_bus_get_property_trivial(
144 "org.freedesktop.systemd1",
152 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
159 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
160 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
168 r
= sd_bus_get_property_strv(
170 "org.freedesktop.systemd1",
172 "org.freedesktop.systemd1.Unit",
177 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
184 static int compare_unit_time(const void *a
, const void *b
) {
185 return compare(((struct unit_times
*)b
)->time
,
186 ((struct unit_times
*)a
)->time
);
189 static int compare_unit_start(const void *a
, const void *b
) {
190 return compare(((struct unit_times
*)a
)->activating
,
191 ((struct unit_times
*)b
)->activating
);
194 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
195 struct unit_times
*p
;
197 for (p
= t
; p
< t
+ n
; p
++)
203 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
212 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
213 static struct boot_times times
;
214 static bool cached
= false;
219 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
221 if (bus_get_uint64_property(bus
,
222 "/org/freedesktop/systemd1",
223 "org.freedesktop.systemd1.Manager",
224 "FirmwareTimestampMonotonic",
225 ×
.firmware_time
) < 0 ||
226 bus_get_uint64_property(bus
,
227 "/org/freedesktop/systemd1",
228 "org.freedesktop.systemd1.Manager",
229 "LoaderTimestampMonotonic",
230 ×
.loader_time
) < 0 ||
231 bus_get_uint64_property(bus
,
232 "/org/freedesktop/systemd1",
233 "org.freedesktop.systemd1.Manager",
235 ×
.kernel_time
) < 0 ||
236 bus_get_uint64_property(bus
,
237 "/org/freedesktop/systemd1",
238 "org.freedesktop.systemd1.Manager",
239 "InitRDTimestampMonotonic",
240 ×
.initrd_time
) < 0 ||
241 bus_get_uint64_property(bus
,
242 "/org/freedesktop/systemd1",
243 "org.freedesktop.systemd1.Manager",
244 "UserspaceTimestampMonotonic",
245 ×
.userspace_time
) < 0 ||
246 bus_get_uint64_property(bus
,
247 "/org/freedesktop/systemd1",
248 "org.freedesktop.systemd1.Manager",
249 "FinishTimestampMonotonic",
250 ×
.finish_time
) < 0 ||
251 bus_get_uint64_property(bus
,
252 "/org/freedesktop/systemd1",
253 "org.freedesktop.systemd1.Manager",
254 "SecurityStartTimestampMonotonic",
255 ×
.security_start_time
) < 0 ||
256 bus_get_uint64_property(bus
,
257 "/org/freedesktop/systemd1",
258 "org.freedesktop.systemd1.Manager",
259 "SecurityFinishTimestampMonotonic",
260 ×
.security_finish_time
) < 0 ||
261 bus_get_uint64_property(bus
,
262 "/org/freedesktop/systemd1",
263 "org.freedesktop.systemd1.Manager",
264 "GeneratorsStartTimestampMonotonic",
265 ×
.generators_start_time
) < 0 ||
266 bus_get_uint64_property(bus
,
267 "/org/freedesktop/systemd1",
268 "org.freedesktop.systemd1.Manager",
269 "GeneratorsFinishTimestampMonotonic",
270 ×
.generators_finish_time
) < 0 ||
271 bus_get_uint64_property(bus
,
272 "/org/freedesktop/systemd1",
273 "org.freedesktop.systemd1.Manager",
274 "UnitsLoadStartTimestampMonotonic",
275 ×
.unitsload_start_time
) < 0 ||
276 bus_get_uint64_property(bus
,
277 "/org/freedesktop/systemd1",
278 "org.freedesktop.systemd1.Manager",
279 "UnitsLoadFinishTimestampMonotonic",
280 ×
.unitsload_finish_time
) < 0)
283 if (times
.finish_time
<= 0) {
284 log_error("Bootup is not yet finished. Please try again later.");
290 * User-instance-specific timestamps processing
291 * (see comment to reverse_offset in struct boot_times).
293 times
.reverse_offset
= times
.userspace_time
;
295 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
296 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
298 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
301 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
302 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
304 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
305 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
307 if (times
.initrd_time
)
308 times
.kernel_done_time
= times
.initrd_time
;
310 times
.kernel_done_time
= times
.userspace_time
;
320 static void free_host_info(struct host_info
*hi
) {
324 free(hi
->kernel_name
);
325 free(hi
->kernel_release
);
326 free(hi
->kernel_version
);
327 free(hi
->os_pretty_name
);
328 free(hi
->virtualization
);
329 free(hi
->architecture
);
332 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
333 #define _cleanup_host_info_ _cleanup_(free_host_infop)
335 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
336 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
337 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
339 struct boot_times
*boot_times
= NULL
;
340 struct unit_times
*unit_times
= NULL
;
344 r
= acquire_boot_times(bus
, &boot_times
);
348 r
= sd_bus_call_method(
350 "org.freedesktop.systemd1",
351 "/org/freedesktop/systemd1",
352 "org.freedesktop.systemd1.Manager",
357 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
361 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
363 bus_log_parse_error(r
);
367 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
368 struct unit_times
*t
;
370 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
378 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
380 if (bus_get_uint64_property(bus
, u
.unit_path
,
381 "org.freedesktop.systemd1.Unit",
382 "InactiveExitTimestampMonotonic",
383 &t
->activating
) < 0 ||
384 bus_get_uint64_property(bus
, u
.unit_path
,
385 "org.freedesktop.systemd1.Unit",
386 "ActiveEnterTimestampMonotonic",
387 &t
->activated
) < 0 ||
388 bus_get_uint64_property(bus
, u
.unit_path
,
389 "org.freedesktop.systemd1.Unit",
390 "ActiveExitTimestampMonotonic",
391 &t
->deactivating
) < 0 ||
392 bus_get_uint64_property(bus
, u
.unit_path
,
393 "org.freedesktop.systemd1.Unit",
394 "InactiveEnterTimestampMonotonic",
395 &t
->deactivated
) < 0) {
400 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
401 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
402 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
403 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
405 if (t
->activated
>= t
->activating
)
406 t
->time
= t
->activated
- t
->activating
;
407 else if (t
->deactivated
>= t
->activating
)
408 t
->time
= t
->deactivated
- t
->activating
;
412 if (t
->activating
== 0)
415 t
->name
= strdup(u
.id
);
416 if (t
->name
== NULL
) {
423 bus_log_parse_error(r
);
432 free_unit_times(unit_times
, (unsigned) c
);
436 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
438 _cleanup_host_info_
struct host_info
*host
;
439 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
441 static const struct bus_properties_map hostname_map
[] = {
442 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
443 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
444 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
445 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
446 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
450 static const struct bus_properties_map manager_map
[] = {
451 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
452 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
456 host
= new0(struct host_info
, 1);
460 r
= bus_map_all_properties(bus
,
461 "org.freedesktop.hostname1",
462 "/org/freedesktop/hostname1",
466 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s",
467 bus_error_message(&error
, r
));
469 r
= bus_map_all_properties(bus
,
470 "org.freedesktop.systemd1",
471 "/org/freedesktop/systemd1",
475 return log_error_errno(r
, "Failed to get host information from systemd: %s",
476 bus_error_message(&error
, r
));
483 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
484 char ts
[FORMAT_TIMESPAN_MAX
];
485 struct boot_times
*t
;
486 static char buf
[4096];
491 r
= acquire_boot_times(bus
, &t
);
498 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
499 if (t
->firmware_time
)
500 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
502 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
504 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
505 if (t
->initrd_time
> 0)
506 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
508 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
509 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
519 static void svg_graph_box(double height
, double begin
, double end
) {
522 /* outside box, fill */
523 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
524 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
526 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
527 /* lines for each second */
528 if (i
% 5000000 == 0)
529 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
530 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
531 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
532 else if (i
% 1000000 == 0)
533 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
534 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
535 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
537 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
538 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
542 static int analyze_plot(sd_bus
*bus
) {
543 struct unit_times
*times
;
544 struct boot_times
*boot
;
545 _cleanup_host_info_
struct host_info
*host
= NULL
;
548 _cleanup_free_
char *pretty_times
= NULL
;
549 struct unit_times
*u
;
551 n
= acquire_boot_times(bus
, &boot
);
555 n
= pretty_boot_time(bus
, &pretty_times
);
559 n
= acquire_host_info(bus
, &host
);
563 n
= acquire_time_data(bus
, ×
);
567 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
569 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
573 if (boot
->firmware_time
> boot
->loader_time
)
575 if (boot
->loader_time
) {
580 if (boot
->initrd_time
)
582 if (boot
->kernel_time
)
585 for (u
= times
; u
< times
+ n
; u
++) {
586 double text_start
, text_width
;
588 if (u
->activating
< boot
->userspace_time
||
589 u
->activating
> boot
->finish_time
) {
590 u
->name
= mfree(u
->name
);
594 /* If the text cannot fit on the left side then
595 * increase the svg width so it fits on the right.
596 * TODO: calculate the text width more accurately */
597 text_width
= 8.0 * strlen(u
->name
);
598 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
599 if (text_width
> text_start
&& text_width
+ text_start
> width
)
600 width
= text_width
+ text_start
;
602 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
603 && u
->activated
== 0 && u
->deactivating
== 0)
604 u
->activated
= u
->deactivating
= u
->deactivated
;
605 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
606 u
->activated
= boot
->finish_time
;
607 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
608 u
->deactivating
= boot
->finish_time
;
609 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
610 u
->deactivated
= boot
->finish_time
;
614 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
615 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
616 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
618 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
619 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
620 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
621 5 * SCALE_Y
/* legend */);
623 /* write some basic info as a comment, including some help */
624 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
625 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
626 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
627 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
628 "<!-- point your browser to this file. -->\n\n"
629 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
632 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
633 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
634 " rect.background { fill: rgb(255,255,255); }\n"
635 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
636 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
637 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
638 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
639 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
640 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
641 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
643 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
644 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
645 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
646 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
647 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
649 " line.sec5 { stroke-width: 2; }\n"
650 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
651 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
652 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
653 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
654 " text.sec { font-size: 10px; }\n"
655 " ]]>\n </style>\n</defs>\n\n");
657 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
658 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
659 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
660 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
661 strempty(host
->hostname
),
662 strempty(host
->kernel_name
),
663 strempty(host
->kernel_release
),
664 strempty(host
->kernel_version
),
665 strempty(host
->architecture
),
666 strempty(host
->virtualization
));
668 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
669 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
671 if (boot
->firmware_time
) {
672 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
673 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
676 if (boot
->loader_time
) {
677 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
678 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
681 if (boot
->kernel_time
) {
682 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
683 svg_text(true, 0, y
, "kernel");
686 if (boot
->initrd_time
) {
687 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
688 svg_text(true, boot
->initrd_time
, y
, "initrd");
691 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
692 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
693 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
694 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
695 svg_text(true, boot
->userspace_time
, y
, "systemd");
698 for (u
= times
; u
< times
+ n
; u
++) {
699 char ts
[FORMAT_TIMESPAN_MAX
];
705 svg_bar("activating", u
->activating
, u
->activated
, y
);
706 svg_bar("active", u
->activated
, u
->deactivating
, y
);
707 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
709 /* place the text on the left if we have passed the half of the svg width */
710 b
= u
->activating
* SCALE_X
< width
/ 2;
712 svg_text(b
, u
->activating
, y
, "%s (%s)",
713 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
715 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
722 svg("<g transform=\"translate(20,100)\">\n");
724 svg_bar("activating", 0, 300000, y
);
725 svg_text(true, 400000, y
, "Activating");
727 svg_bar("active", 0, 300000, y
);
728 svg_text(true, 400000, y
, "Active");
730 svg_bar("deactivating", 0, 300000, y
);
731 svg_text(true, 400000, y
, "Deactivating");
733 svg_bar("security", 0, 300000, y
);
734 svg_text(true, 400000, y
, "Setting up security module");
736 svg_bar("generators", 0, 300000, y
);
737 svg_text(true, 400000, y
, "Generators");
739 svg_bar("unitsload", 0, 300000, y
);
740 svg_text(true, 400000, y
, "Loading unit files");
747 free_unit_times(times
, (unsigned) n
);
753 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
754 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
756 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
758 for (i
= level
; i
!= 0; i
--)
759 printf("%s", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
761 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_TREE_BRANCH
));
765 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED
, name
,
766 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
767 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
768 else if (times
->activated
> boot
->userspace_time
)
769 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
779 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
780 _cleanup_free_
char *path
= NULL
;
786 path
= unit_dbus_path_from_name(name
);
790 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
793 static Hashmap
*unit_times_hashmap
;
795 static int list_dependencies_compare(const void *_a
, const void *_b
) {
796 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
797 usec_t usa
= 0, usb
= 0;
798 struct unit_times
*times
;
800 times
= hashmap_get(unit_times_hashmap
, *a
);
802 usa
= times
->activated
;
803 times
= hashmap_get(unit_times_hashmap
, *b
);
805 usb
= times
->activated
;
810 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
811 unsigned int branches
) {
812 _cleanup_strv_free_
char **deps
= NULL
;
815 usec_t service_longest
= 0;
817 struct unit_times
*times
;
818 struct boot_times
*boot
;
820 if (strv_extend(units
, name
))
823 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
827 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
829 r
= acquire_boot_times(bus
, &boot
);
833 STRV_FOREACH(c
, deps
) {
834 times
= hashmap_get(unit_times_hashmap
, *c
);
837 && times
->activated
<= boot
->finish_time
838 && (times
->activated
>= service_longest
839 || service_longest
== 0)) {
840 service_longest
= times
->activated
;
845 if (service_longest
== 0 )
848 STRV_FOREACH(c
, deps
) {
849 times
= hashmap_get(unit_times_hashmap
, *c
);
850 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
857 STRV_FOREACH(c
, deps
) {
858 times
= hashmap_get(unit_times_hashmap
, *c
);
861 || times
->activated
> boot
->finish_time
862 || service_longest
- times
->activated
> arg_fuzz
)
867 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
871 if (strv_contains(*units
, *c
)) {
872 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
879 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
880 (branches
<< 1) | (to_print
? 1 : 0));
890 static int list_dependencies(sd_bus
*bus
, const char *name
) {
891 _cleanup_strv_free_
char **units
= NULL
;
892 char ts
[FORMAT_TIMESPAN_MAX
];
893 struct unit_times
*times
;
896 _cleanup_free_
char *path
= NULL
;
897 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
898 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
899 struct boot_times
*boot
;
903 path
= unit_dbus_path_from_name(name
);
907 r
= sd_bus_get_property(
909 "org.freedesktop.systemd1",
911 "org.freedesktop.systemd1.Unit",
917 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
921 r
= sd_bus_message_read(reply
, "s", &id
);
923 return bus_log_parse_error(r
);
925 times
= hashmap_get(unit_times_hashmap
, id
);
927 r
= acquire_boot_times(bus
, &boot
);
933 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED
, id
,
934 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
935 else if (times
->activated
> boot
->userspace_time
)
936 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
941 return list_dependencies_one(bus
, name
, 0, &units
, 0);
944 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
945 struct unit_times
*times
;
950 n
= acquire_time_data(bus
, ×
);
954 h
= hashmap_new(&string_hash_ops
);
958 for (i
= 0; i
< (unsigned)n
; i
++) {
959 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
963 unit_times_hashmap
= h
;
965 pager_open_if_enabled();
967 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
968 "The time the unit takes to start is printed after the \"+\" character.\n");
970 if (!strv_isempty(names
)) {
972 STRV_FOREACH(name
, names
)
973 list_dependencies(bus
, *name
);
975 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
978 free_unit_times(times
, (unsigned) n
);
982 static int analyze_blame(sd_bus
*bus
) {
983 struct unit_times
*times
;
987 n
= acquire_time_data(bus
, ×
);
991 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
993 pager_open_if_enabled();
995 for (i
= 0; i
< (unsigned) n
; i
++) {
996 char ts
[FORMAT_TIMESPAN_MAX
];
998 if (times
[i
].time
> 0)
999 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1002 free_unit_times(times
, (unsigned) n
);
1006 static int analyze_time(sd_bus
*bus
) {
1007 _cleanup_free_
char *buf
= NULL
;
1010 r
= pretty_boot_time(bus
, &buf
);
1018 static int graph_one_property(sd_bus
*bus
, const UnitInfo
*u
, const char* prop
, const char *color
, char* patterns
[], char* from_patterns
[], char* to_patterns
[]) {
1019 _cleanup_strv_free_
char **units
= NULL
;
1022 bool match_patterns
;
1028 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1030 if (!strv_isempty(from_patterns
) &&
1032 !strv_fnmatch(from_patterns
, u
->id
, 0))
1035 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1039 STRV_FOREACH(unit
, units
) {
1040 bool match_patterns2
;
1042 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1044 if (!strv_isempty(to_patterns
) &&
1046 !strv_fnmatch(to_patterns
, *unit
, 0))
1049 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1052 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1058 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1064 if (arg_dot
== DEP_ORDER
||arg_dot
== DEP_ALL
) {
1065 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1070 if (arg_dot
== DEP_REQUIRE
||arg_dot
== DEP_ALL
) {
1071 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1074 r
= graph_one_property(bus
, u
, "RequiresOverridable", "black", patterns
, from_patterns
, to_patterns
);
1077 r
= graph_one_property(bus
, u
, "RequisiteOverridable", "darkblue", patterns
, from_patterns
, to_patterns
);
1080 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1083 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1086 r
= graph_one_property(bus
, u
, "ConflictedBy", "red", patterns
, from_patterns
, to_patterns
);
1094 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1095 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1099 STRV_FOREACH(pattern
, patterns
) {
1100 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1101 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1103 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1106 if (string_is_glob(*pattern
))
1109 unit
= unit_dbus_path_from_name(*pattern
);
1113 r
= sd_bus_get_property_string(
1115 "org.freedesktop.systemd1",
1117 "org.freedesktop.systemd1.Unit",
1122 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1124 if (!streq(*pattern
, unit_id
)) {
1125 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1130 *ret
= expanded_patterns
;
1131 expanded_patterns
= NULL
; /* do not free */
1136 static int dot(sd_bus
*bus
, char* patterns
[]) {
1137 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1138 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1139 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1140 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1141 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1145 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1149 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1153 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1157 r
= sd_bus_call_method(
1159 "org.freedesktop.systemd1",
1160 "/org/freedesktop/systemd1",
1161 "org.freedesktop.systemd1.Manager",
1167 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1171 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1173 return bus_log_parse_error(r
);
1175 printf("digraph systemd {\n");
1177 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1179 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1184 return bus_log_parse_error(r
);
1188 log_info(" Color legend: black = Requires\n"
1189 " dark blue = Requisite\n"
1190 " dark grey = Wants\n"
1191 " red = Conflicts\n"
1192 " green = After\n");
1195 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1196 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1201 static int dump(sd_bus
*bus
, char **args
) {
1202 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1203 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1204 const char *text
= NULL
;
1207 if (!strv_isempty(args
)) {
1208 log_error("Too many arguments.");
1212 pager_open_if_enabled();
1214 r
= sd_bus_call_method(
1216 "org.freedesktop.systemd1",
1217 "/org/freedesktop/systemd1",
1218 "org.freedesktop.systemd1.Manager",
1224 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1226 r
= sd_bus_message_read(reply
, "s", &text
);
1228 return bus_log_parse_error(r
);
1230 fputs(text
, stdout
);
1234 static int set_log_level(sd_bus
*bus
, char **args
) {
1235 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1241 if (strv_length(args
) != 1) {
1242 log_error("This command expects one argument only.");
1246 r
= sd_bus_set_property(
1248 "org.freedesktop.systemd1",
1249 "/org/freedesktop/systemd1",
1250 "org.freedesktop.systemd1.Manager",
1256 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1261 static int set_log_target(sd_bus
*bus
, char **args
) {
1262 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1268 if (strv_length(args
) != 1) {
1269 log_error("This command expects one argument only.");
1273 r
= sd_bus_set_property(
1275 "org.freedesktop.systemd1",
1276 "/org/freedesktop/systemd1",
1277 "org.freedesktop.systemd1.Manager",
1283 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1288 static void help(void) {
1290 pager_open_if_enabled();
1292 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1293 "Profile systemd, show unit dependencies, check unit files.\n\n"
1294 " -h --help Show this help\n"
1295 " --version Show package version\n"
1296 " --no-pager Do not pipe output into a pager\n"
1297 " --system Operate on system systemd instance\n"
1298 " --user Operate on user systemd instance\n"
1299 " -H --host=[USER@]HOST Operate on remote host\n"
1300 " -M --machine=CONTAINER Operate on local container\n"
1301 " --order Show only order in the graph\n"
1302 " --require Show only requirement in the graph\n"
1303 " --from-pattern=GLOB Show only origins in the graph\n"
1304 " --to-pattern=GLOB Show only destinations in the graph\n"
1305 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1306 " earlier than the latest in the branch\n"
1307 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1309 " time Print time spent in the kernel\n"
1310 " blame Print list of running units ordered by time to init\n"
1311 " critical-chain Print a tree of the time critical chain of units\n"
1312 " plot Output SVG graphic showing service initialization\n"
1313 " dot Output dependency graph in dot(1) format\n"
1314 " set-log-level LEVEL Set logging threshold for manager\n"
1315 " set-log-target TARGET Set logging target for manager\n"
1316 " dump Output state serialization of service manager\n"
1317 " verify FILE... Check unit files for correctness\n"
1318 , program_invocation_short_name
);
1320 /* When updating this list, including descriptions, apply
1321 * changes to shell-completion/bash/systemd-analyze and
1322 * shell-completion/zsh/_systemd-analyze too. */
1325 static int parse_argv(int argc
, char *argv
[]) {
1327 ARG_VERSION
= 0x100,
1332 ARG_DOT_FROM_PATTERN
,
1339 static const struct option options
[] = {
1340 { "help", no_argument
, NULL
, 'h' },
1341 { "version", no_argument
, NULL
, ARG_VERSION
},
1342 { "order", no_argument
, NULL
, ARG_ORDER
},
1343 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1344 { "user", no_argument
, NULL
, ARG_USER
},
1345 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1346 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1347 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1348 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1349 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1350 { "man", optional_argument
, NULL
, ARG_MAN
},
1351 { "host", required_argument
, NULL
, 'H' },
1352 { "machine", required_argument
, NULL
, 'M' },
1361 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1380 arg_dot
= DEP_ORDER
;
1384 arg_dot
= DEP_REQUIRE
;
1387 case ARG_DOT_FROM_PATTERN
:
1388 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1393 case ARG_DOT_TO_PATTERN
:
1394 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1400 r
= parse_sec(optarg
, &arg_fuzz
);
1406 arg_no_pager
= true;
1410 arg_transport
= BUS_TRANSPORT_REMOTE
;
1415 arg_transport
= BUS_TRANSPORT_MACHINE
;
1421 r
= parse_boolean(optarg
);
1423 log_error("Failed to parse --man= argument.");
1437 assert_not_reached("Unhandled option code.");
1440 return 1; /* work to do */
1443 int main(int argc
, char *argv
[]) {
1446 setlocale(LC_ALL
, "");
1447 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1448 log_parse_environment();
1451 r
= parse_argv(argc
, argv
);
1455 if (streq_ptr(argv
[optind
], "verify"))
1456 r
= verify_units(argv
+optind
+1,
1457 arg_user
? MANAGER_USER
: MANAGER_SYSTEM
,
1460 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1462 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1464 log_error_errno(r
, "Failed to create bus connection: %m");
1468 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1469 r
= analyze_time(bus
);
1470 else if (streq(argv
[optind
], "blame"))
1471 r
= analyze_blame(bus
);
1472 else if (streq(argv
[optind
], "critical-chain"))
1473 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1474 else if (streq(argv
[optind
], "plot"))
1475 r
= analyze_plot(bus
);
1476 else if (streq(argv
[optind
], "dot"))
1477 r
= dot(bus
, argv
+optind
+1);
1478 else if (streq(argv
[optind
], "dump"))
1479 r
= dump(bus
, argv
+optind
+1);
1480 else if (streq(argv
[optind
], "set-log-level"))
1481 r
= set_log_level(bus
, argv
+optind
+1);
1482 else if (streq(argv
[optind
], "set-log-target"))
1483 r
= set_log_target(bus
, argv
+optind
+1);
1485 log_error("Unknown operation '%s'.", argv
[optind
]);
1491 strv_free(arg_dot_from_patterns
);
1492 strv_free(arg_dot_to_patterns
);
1494 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;