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"
36 #include "parse-util.h"
40 #include "terminal-util.h"
41 #include "unit-name.h"
44 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
45 #define SCALE_Y (20.0)
47 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
49 #define svg(...) printf(__VA_ARGS__)
51 #define svg_bar(class, x1, x2, y) \
52 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
54 SCALE_X * (x1), SCALE_Y * (y), \
55 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
57 #define svg_text(b, x, y, format, ...) \
59 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
60 svg(format, ## __VA_ARGS__); \
69 static char** arg_dot_from_patterns
= NULL
;
70 static char** arg_dot_to_patterns
= NULL
;
71 static usec_t arg_fuzz
= 0;
72 static bool arg_no_pager
= false;
73 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
74 static char *arg_host
= NULL
;
75 static bool arg_user
= false;
76 static bool arg_man
= true;
82 usec_t kernel_done_time
;
84 usec_t userspace_time
;
86 usec_t security_start_time
;
87 usec_t security_finish_time
;
88 usec_t generators_start_time
;
89 usec_t generators_finish_time
;
90 usec_t unitsload_start_time
;
91 usec_t unitsload_finish_time
;
94 * If we're analyzing the user instance, all timestamps will be offset
95 * by its own start-up timestamp, which may be arbitrarily big.
96 * With "plot", this causes arbitrarily wide output SVG files which almost
97 * completely consist of empty space. Thus we cancel out this offset.
99 * This offset is subtracted from times above by acquire_boot_times(),
100 * but it still needs to be subtracted from unit-specific timestamps
101 * (so it is stored here for reference).
103 usec_t reverse_offset
;
118 char *kernel_release
;
119 char *kernel_version
;
120 char *os_pretty_name
;
121 char *virtualization
;
125 static void pager_open_if_enabled(void) {
133 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
134 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
143 r
= sd_bus_get_property_trivial(
145 "org.freedesktop.systemd1",
153 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
160 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
161 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
169 r
= sd_bus_get_property_strv(
171 "org.freedesktop.systemd1",
173 "org.freedesktop.systemd1.Unit",
178 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
185 static int compare_unit_time(const void *a
, const void *b
) {
186 return compare(((struct unit_times
*)b
)->time
,
187 ((struct unit_times
*)a
)->time
);
190 static int compare_unit_start(const void *a
, const void *b
) {
191 return compare(((struct unit_times
*)a
)->activating
,
192 ((struct unit_times
*)b
)->activating
);
195 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
196 struct unit_times
*p
;
198 for (p
= t
; p
< t
+ n
; p
++)
204 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
213 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
214 static struct boot_times times
;
215 static bool cached
= false;
220 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
222 if (bus_get_uint64_property(bus
,
223 "/org/freedesktop/systemd1",
224 "org.freedesktop.systemd1.Manager",
225 "FirmwareTimestampMonotonic",
226 ×
.firmware_time
) < 0 ||
227 bus_get_uint64_property(bus
,
228 "/org/freedesktop/systemd1",
229 "org.freedesktop.systemd1.Manager",
230 "LoaderTimestampMonotonic",
231 ×
.loader_time
) < 0 ||
232 bus_get_uint64_property(bus
,
233 "/org/freedesktop/systemd1",
234 "org.freedesktop.systemd1.Manager",
236 ×
.kernel_time
) < 0 ||
237 bus_get_uint64_property(bus
,
238 "/org/freedesktop/systemd1",
239 "org.freedesktop.systemd1.Manager",
240 "InitRDTimestampMonotonic",
241 ×
.initrd_time
) < 0 ||
242 bus_get_uint64_property(bus
,
243 "/org/freedesktop/systemd1",
244 "org.freedesktop.systemd1.Manager",
245 "UserspaceTimestampMonotonic",
246 ×
.userspace_time
) < 0 ||
247 bus_get_uint64_property(bus
,
248 "/org/freedesktop/systemd1",
249 "org.freedesktop.systemd1.Manager",
250 "FinishTimestampMonotonic",
251 ×
.finish_time
) < 0 ||
252 bus_get_uint64_property(bus
,
253 "/org/freedesktop/systemd1",
254 "org.freedesktop.systemd1.Manager",
255 "SecurityStartTimestampMonotonic",
256 ×
.security_start_time
) < 0 ||
257 bus_get_uint64_property(bus
,
258 "/org/freedesktop/systemd1",
259 "org.freedesktop.systemd1.Manager",
260 "SecurityFinishTimestampMonotonic",
261 ×
.security_finish_time
) < 0 ||
262 bus_get_uint64_property(bus
,
263 "/org/freedesktop/systemd1",
264 "org.freedesktop.systemd1.Manager",
265 "GeneratorsStartTimestampMonotonic",
266 ×
.generators_start_time
) < 0 ||
267 bus_get_uint64_property(bus
,
268 "/org/freedesktop/systemd1",
269 "org.freedesktop.systemd1.Manager",
270 "GeneratorsFinishTimestampMonotonic",
271 ×
.generators_finish_time
) < 0 ||
272 bus_get_uint64_property(bus
,
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "UnitsLoadStartTimestampMonotonic",
276 ×
.unitsload_start_time
) < 0 ||
277 bus_get_uint64_property(bus
,
278 "/org/freedesktop/systemd1",
279 "org.freedesktop.systemd1.Manager",
280 "UnitsLoadFinishTimestampMonotonic",
281 ×
.unitsload_finish_time
) < 0)
284 if (times
.finish_time
<= 0) {
285 log_error("Bootup is not yet finished. Please try again later.");
291 * User-instance-specific timestamps processing
292 * (see comment to reverse_offset in struct boot_times).
294 times
.reverse_offset
= times
.userspace_time
;
296 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
297 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
302 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
303 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
305 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
306 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
308 if (times
.initrd_time
)
309 times
.kernel_done_time
= times
.initrd_time
;
311 times
.kernel_done_time
= times
.userspace_time
;
321 static void free_host_info(struct host_info
*hi
) {
327 free(hi
->kernel_name
);
328 free(hi
->kernel_release
);
329 free(hi
->kernel_version
);
330 free(hi
->os_pretty_name
);
331 free(hi
->virtualization
);
332 free(hi
->architecture
);
336 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
338 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
339 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
340 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
342 struct boot_times
*boot_times
= NULL
;
343 struct unit_times
*unit_times
= NULL
;
347 r
= acquire_boot_times(bus
, &boot_times
);
351 r
= sd_bus_call_method(
353 "org.freedesktop.systemd1",
354 "/org/freedesktop/systemd1",
355 "org.freedesktop.systemd1.Manager",
360 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
364 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
366 bus_log_parse_error(r
);
370 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
371 struct unit_times
*t
;
373 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
381 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
383 if (bus_get_uint64_property(bus
, u
.unit_path
,
384 "org.freedesktop.systemd1.Unit",
385 "InactiveExitTimestampMonotonic",
386 &t
->activating
) < 0 ||
387 bus_get_uint64_property(bus
, u
.unit_path
,
388 "org.freedesktop.systemd1.Unit",
389 "ActiveEnterTimestampMonotonic",
390 &t
->activated
) < 0 ||
391 bus_get_uint64_property(bus
, u
.unit_path
,
392 "org.freedesktop.systemd1.Unit",
393 "ActiveExitTimestampMonotonic",
394 &t
->deactivating
) < 0 ||
395 bus_get_uint64_property(bus
, u
.unit_path
,
396 "org.freedesktop.systemd1.Unit",
397 "InactiveEnterTimestampMonotonic",
398 &t
->deactivated
) < 0) {
403 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
404 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
405 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
406 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
408 if (t
->activated
>= t
->activating
)
409 t
->time
= t
->activated
- t
->activating
;
410 else if (t
->deactivated
>= t
->activating
)
411 t
->time
= t
->deactivated
- t
->activating
;
415 if (t
->activating
== 0)
418 t
->name
= strdup(u
.id
);
419 if (t
->name
== NULL
) {
426 bus_log_parse_error(r
);
435 free_unit_times(unit_times
, (unsigned) c
);
439 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
440 static const struct bus_properties_map hostname_map
[] = {
441 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
442 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
443 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
444 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
445 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
449 static const struct bus_properties_map manager_map
[] = {
450 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
451 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
455 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
456 _cleanup_(free_host_infop
) struct host_info
*host
;
459 host
= new0(struct host_info
, 1);
463 r
= bus_map_all_properties(bus
,
464 "org.freedesktop.hostname1",
465 "/org/freedesktop/hostname1",
469 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
471 r
= bus_map_all_properties(bus
,
472 "org.freedesktop.systemd1",
473 "/org/freedesktop/systemd1",
477 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
485 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
486 char ts
[FORMAT_TIMESPAN_MAX
];
487 struct boot_times
*t
;
488 static char buf
[4096];
493 r
= acquire_boot_times(bus
, &t
);
500 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
501 if (t
->firmware_time
)
502 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
504 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
506 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
507 if (t
->initrd_time
> 0)
508 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
510 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
511 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
521 static void svg_graph_box(double height
, double begin
, double end
) {
524 /* outside box, fill */
525 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
526 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
528 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
529 /* lines for each second */
530 if (i
% 5000000 == 0)
531 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
532 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
533 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
534 else if (i
% 1000000 == 0)
535 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
536 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
537 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
539 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
540 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
544 static int analyze_plot(sd_bus
*bus
) {
545 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
546 struct unit_times
*times
;
547 struct boot_times
*boot
;
550 _cleanup_free_
char *pretty_times
= NULL
;
551 struct unit_times
*u
;
553 n
= acquire_boot_times(bus
, &boot
);
557 n
= pretty_boot_time(bus
, &pretty_times
);
561 n
= acquire_host_info(bus
, &host
);
565 n
= acquire_time_data(bus
, ×
);
569 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
571 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
575 if (boot
->firmware_time
> boot
->loader_time
)
577 if (boot
->loader_time
) {
582 if (boot
->initrd_time
)
584 if (boot
->kernel_time
)
587 for (u
= times
; u
< times
+ n
; u
++) {
588 double text_start
, text_width
;
590 if (u
->activating
< boot
->userspace_time
||
591 u
->activating
> boot
->finish_time
) {
592 u
->name
= mfree(u
->name
);
596 /* If the text cannot fit on the left side then
597 * increase the svg width so it fits on the right.
598 * TODO: calculate the text width more accurately */
599 text_width
= 8.0 * strlen(u
->name
);
600 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
601 if (text_width
> text_start
&& text_width
+ text_start
> width
)
602 width
= text_width
+ text_start
;
604 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
605 && u
->activated
== 0 && u
->deactivating
== 0)
606 u
->activated
= u
->deactivating
= u
->deactivated
;
607 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
608 u
->activated
= boot
->finish_time
;
609 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
610 u
->deactivating
= boot
->finish_time
;
611 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
612 u
->deactivated
= boot
->finish_time
;
616 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
617 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
618 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
620 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
621 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
622 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
623 5 * SCALE_Y
/* legend */);
625 /* write some basic info as a comment, including some help */
626 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
627 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
628 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
629 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
630 "<!-- point your browser to this file. -->\n\n"
631 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
634 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
635 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
636 " rect.background { fill: rgb(255,255,255); }\n"
637 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
638 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
639 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
640 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
641 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
643 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
644 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
645 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
646 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
647 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
648 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
649 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
651 " line.sec5 { stroke-width: 2; }\n"
652 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
653 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
654 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
655 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
656 " text.sec { font-size: 10px; }\n"
657 " ]]>\n </style>\n</defs>\n\n");
659 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
660 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
661 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
662 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
663 strempty(host
->hostname
),
664 strempty(host
->kernel_name
),
665 strempty(host
->kernel_release
),
666 strempty(host
->kernel_version
),
667 strempty(host
->architecture
),
668 strempty(host
->virtualization
));
670 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
671 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
673 if (boot
->firmware_time
) {
674 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
675 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
678 if (boot
->loader_time
) {
679 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
680 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
683 if (boot
->kernel_time
) {
684 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
685 svg_text(true, 0, y
, "kernel");
688 if (boot
->initrd_time
) {
689 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
690 svg_text(true, boot
->initrd_time
, y
, "initrd");
693 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
694 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
695 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
696 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
697 svg_text(true, boot
->userspace_time
, y
, "systemd");
700 for (u
= times
; u
< times
+ n
; u
++) {
701 char ts
[FORMAT_TIMESPAN_MAX
];
707 svg_bar("activating", u
->activating
, u
->activated
, y
);
708 svg_bar("active", u
->activated
, u
->deactivating
, y
);
709 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
711 /* place the text on the left if we have passed the half of the svg width */
712 b
= u
->activating
* SCALE_X
< width
/ 2;
714 svg_text(b
, u
->activating
, y
, "%s (%s)",
715 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
717 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
724 svg("<g transform=\"translate(20,100)\">\n");
726 svg_bar("activating", 0, 300000, y
);
727 svg_text(true, 400000, y
, "Activating");
729 svg_bar("active", 0, 300000, y
);
730 svg_text(true, 400000, y
, "Active");
732 svg_bar("deactivating", 0, 300000, y
);
733 svg_text(true, 400000, y
, "Deactivating");
735 svg_bar("security", 0, 300000, y
);
736 svg_text(true, 400000, y
, "Setting up security module");
738 svg_bar("generators", 0, 300000, y
);
739 svg_text(true, 400000, y
, "Generators");
741 svg_bar("unitsload", 0, 300000, y
);
742 svg_text(true, 400000, y
, "Loading unit files");
749 free_unit_times(times
, (unsigned) n
);
755 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
756 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
758 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
760 for (i
= level
; i
!= 0; i
--)
761 printf("%s", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
763 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_TREE_BRANCH
));
767 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED
, name
,
768 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
769 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
770 else if (times
->activated
> boot
->userspace_time
)
771 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
781 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
782 _cleanup_free_
char *path
= NULL
;
788 path
= unit_dbus_path_from_name(name
);
792 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
795 static Hashmap
*unit_times_hashmap
;
797 static int list_dependencies_compare(const void *_a
, const void *_b
) {
798 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
799 usec_t usa
= 0, usb
= 0;
800 struct unit_times
*times
;
802 times
= hashmap_get(unit_times_hashmap
, *a
);
804 usa
= times
->activated
;
805 times
= hashmap_get(unit_times_hashmap
, *b
);
807 usb
= times
->activated
;
812 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
813 unsigned int branches
) {
814 _cleanup_strv_free_
char **deps
= NULL
;
817 usec_t service_longest
= 0;
819 struct unit_times
*times
;
820 struct boot_times
*boot
;
822 if (strv_extend(units
, name
))
825 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
829 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
831 r
= acquire_boot_times(bus
, &boot
);
835 STRV_FOREACH(c
, deps
) {
836 times
= hashmap_get(unit_times_hashmap
, *c
);
839 && times
->activated
<= boot
->finish_time
840 && (times
->activated
>= service_longest
841 || service_longest
== 0)) {
842 service_longest
= times
->activated
;
847 if (service_longest
== 0 )
850 STRV_FOREACH(c
, deps
) {
851 times
= hashmap_get(unit_times_hashmap
, *c
);
852 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
859 STRV_FOREACH(c
, deps
) {
860 times
= hashmap_get(unit_times_hashmap
, *c
);
863 || times
->activated
> boot
->finish_time
864 || service_longest
- times
->activated
> arg_fuzz
)
869 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
873 if (strv_contains(*units
, *c
)) {
874 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
881 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
882 (branches
<< 1) | (to_print
? 1 : 0));
892 static int list_dependencies(sd_bus
*bus
, const char *name
) {
893 _cleanup_strv_free_
char **units
= NULL
;
894 char ts
[FORMAT_TIMESPAN_MAX
];
895 struct unit_times
*times
;
898 _cleanup_free_
char *path
= NULL
;
899 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
900 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
901 struct boot_times
*boot
;
905 path
= unit_dbus_path_from_name(name
);
909 r
= sd_bus_get_property(
911 "org.freedesktop.systemd1",
913 "org.freedesktop.systemd1.Unit",
919 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
923 r
= sd_bus_message_read(reply
, "s", &id
);
925 return bus_log_parse_error(r
);
927 times
= hashmap_get(unit_times_hashmap
, id
);
929 r
= acquire_boot_times(bus
, &boot
);
935 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED
, id
,
936 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
937 else if (times
->activated
> boot
->userspace_time
)
938 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
943 return list_dependencies_one(bus
, name
, 0, &units
, 0);
946 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
947 struct unit_times
*times
;
952 n
= acquire_time_data(bus
, ×
);
956 h
= hashmap_new(&string_hash_ops
);
960 for (i
= 0; i
< (unsigned)n
; i
++) {
961 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
965 unit_times_hashmap
= h
;
967 pager_open_if_enabled();
969 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
970 "The time the unit takes to start is printed after the \"+\" character.\n");
972 if (!strv_isempty(names
)) {
974 STRV_FOREACH(name
, names
)
975 list_dependencies(bus
, *name
);
977 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
980 free_unit_times(times
, (unsigned) n
);
984 static int analyze_blame(sd_bus
*bus
) {
985 struct unit_times
*times
;
989 n
= acquire_time_data(bus
, ×
);
993 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
995 pager_open_if_enabled();
997 for (i
= 0; i
< (unsigned) n
; i
++) {
998 char ts
[FORMAT_TIMESPAN_MAX
];
1000 if (times
[i
].time
> 0)
1001 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1004 free_unit_times(times
, (unsigned) n
);
1008 static int analyze_time(sd_bus
*bus
) {
1009 _cleanup_free_
char *buf
= NULL
;
1012 r
= pretty_boot_time(bus
, &buf
);
1020 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
[]) {
1021 _cleanup_strv_free_
char **units
= NULL
;
1024 bool match_patterns
;
1030 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1032 if (!strv_isempty(from_patterns
) &&
1034 !strv_fnmatch(from_patterns
, u
->id
, 0))
1037 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1041 STRV_FOREACH(unit
, units
) {
1042 bool match_patterns2
;
1044 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1046 if (!strv_isempty(to_patterns
) &&
1048 !strv_fnmatch(to_patterns
, *unit
, 0))
1051 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1054 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1060 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1066 if (arg_dot
== DEP_ORDER
||arg_dot
== DEP_ALL
) {
1067 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1072 if (arg_dot
== DEP_REQUIRE
||arg_dot
== DEP_ALL
) {
1073 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1076 r
= graph_one_property(bus
, u
, "RequiresOverridable", "black", patterns
, from_patterns
, to_patterns
);
1079 r
= graph_one_property(bus
, u
, "RequisiteOverridable", "darkblue", patterns
, from_patterns
, to_patterns
);
1082 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1085 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1088 r
= graph_one_property(bus
, u
, "ConflictedBy", "red", patterns
, from_patterns
, to_patterns
);
1096 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1097 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1101 STRV_FOREACH(pattern
, patterns
) {
1102 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1103 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1105 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1108 if (string_is_glob(*pattern
))
1111 unit
= unit_dbus_path_from_name(*pattern
);
1115 r
= sd_bus_get_property_string(
1117 "org.freedesktop.systemd1",
1119 "org.freedesktop.systemd1.Unit",
1124 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1126 if (!streq(*pattern
, unit_id
)) {
1127 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1132 *ret
= expanded_patterns
;
1133 expanded_patterns
= NULL
; /* do not free */
1138 static int dot(sd_bus
*bus
, char* patterns
[]) {
1139 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1140 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1141 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1142 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1143 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1147 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1151 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1155 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1159 r
= sd_bus_call_method(
1161 "org.freedesktop.systemd1",
1162 "/org/freedesktop/systemd1",
1163 "org.freedesktop.systemd1.Manager",
1169 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1173 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1175 return bus_log_parse_error(r
);
1177 printf("digraph systemd {\n");
1179 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1181 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1186 return bus_log_parse_error(r
);
1190 log_info(" Color legend: black = Requires\n"
1191 " dark blue = Requisite\n"
1192 " dark grey = Wants\n"
1193 " red = Conflicts\n"
1194 " green = After\n");
1197 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1198 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1203 static int dump(sd_bus
*bus
, char **args
) {
1204 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1205 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1206 const char *text
= NULL
;
1209 if (!strv_isempty(args
)) {
1210 log_error("Too many arguments.");
1214 pager_open_if_enabled();
1216 r
= sd_bus_call_method(
1218 "org.freedesktop.systemd1",
1219 "/org/freedesktop/systemd1",
1220 "org.freedesktop.systemd1.Manager",
1226 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1228 r
= sd_bus_message_read(reply
, "s", &text
);
1230 return bus_log_parse_error(r
);
1232 fputs(text
, stdout
);
1236 static int set_log_level(sd_bus
*bus
, char **args
) {
1237 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1243 if (strv_length(args
) != 1) {
1244 log_error("This command expects one argument only.");
1248 r
= sd_bus_set_property(
1250 "org.freedesktop.systemd1",
1251 "/org/freedesktop/systemd1",
1252 "org.freedesktop.systemd1.Manager",
1258 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1263 static int set_log_target(sd_bus
*bus
, char **args
) {
1264 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1270 if (strv_length(args
) != 1) {
1271 log_error("This command expects one argument only.");
1275 r
= sd_bus_set_property(
1277 "org.freedesktop.systemd1",
1278 "/org/freedesktop/systemd1",
1279 "org.freedesktop.systemd1.Manager",
1285 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1290 static void help(void) {
1292 pager_open_if_enabled();
1294 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1295 "Profile systemd, show unit dependencies, check unit files.\n\n"
1296 " -h --help Show this help\n"
1297 " --version Show package version\n"
1298 " --no-pager Do not pipe output into a pager\n"
1299 " --system Operate on system systemd instance\n"
1300 " --user Operate on user systemd instance\n"
1301 " -H --host=[USER@]HOST Operate on remote host\n"
1302 " -M --machine=CONTAINER Operate on local container\n"
1303 " --order Show only order in the graph\n"
1304 " --require Show only requirement in the graph\n"
1305 " --from-pattern=GLOB Show only origins in the graph\n"
1306 " --to-pattern=GLOB Show only destinations in the graph\n"
1307 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1308 " earlier than the latest in the branch\n"
1309 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1311 " time Print time spent in the kernel\n"
1312 " blame Print list of running units ordered by time to init\n"
1313 " critical-chain Print a tree of the time critical chain of units\n"
1314 " plot Output SVG graphic showing service initialization\n"
1315 " dot Output dependency graph in dot(1) format\n"
1316 " set-log-level LEVEL Set logging threshold for manager\n"
1317 " set-log-target TARGET Set logging target for manager\n"
1318 " dump Output state serialization of service manager\n"
1319 " verify FILE... Check unit files for correctness\n"
1320 , program_invocation_short_name
);
1322 /* When updating this list, including descriptions, apply
1323 * changes to shell-completion/bash/systemd-analyze and
1324 * shell-completion/zsh/_systemd-analyze too. */
1327 static int parse_argv(int argc
, char *argv
[]) {
1329 ARG_VERSION
= 0x100,
1334 ARG_DOT_FROM_PATTERN
,
1341 static const struct option options
[] = {
1342 { "help", no_argument
, NULL
, 'h' },
1343 { "version", no_argument
, NULL
, ARG_VERSION
},
1344 { "order", no_argument
, NULL
, ARG_ORDER
},
1345 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1346 { "user", no_argument
, NULL
, ARG_USER
},
1347 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1348 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1349 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1350 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1351 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1352 { "man", optional_argument
, NULL
, ARG_MAN
},
1353 { "host", required_argument
, NULL
, 'H' },
1354 { "machine", required_argument
, NULL
, 'M' },
1363 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1382 arg_dot
= DEP_ORDER
;
1386 arg_dot
= DEP_REQUIRE
;
1389 case ARG_DOT_FROM_PATTERN
:
1390 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1395 case ARG_DOT_TO_PATTERN
:
1396 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1402 r
= parse_sec(optarg
, &arg_fuzz
);
1408 arg_no_pager
= true;
1412 arg_transport
= BUS_TRANSPORT_REMOTE
;
1417 arg_transport
= BUS_TRANSPORT_MACHINE
;
1423 r
= parse_boolean(optarg
);
1425 log_error("Failed to parse --man= argument.");
1439 assert_not_reached("Unhandled option code.");
1442 return 1; /* work to do */
1445 int main(int argc
, char *argv
[]) {
1448 setlocale(LC_ALL
, "");
1449 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1450 log_parse_environment();
1453 r
= parse_argv(argc
, argv
);
1457 if (streq_ptr(argv
[optind
], "verify"))
1458 r
= verify_units(argv
+optind
+1,
1459 arg_user
? MANAGER_USER
: MANAGER_SYSTEM
,
1462 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1464 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1466 log_error_errno(r
, "Failed to create bus connection: %m");
1470 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1471 r
= analyze_time(bus
);
1472 else if (streq(argv
[optind
], "blame"))
1473 r
= analyze_blame(bus
);
1474 else if (streq(argv
[optind
], "critical-chain"))
1475 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1476 else if (streq(argv
[optind
], "plot"))
1477 r
= analyze_plot(bus
);
1478 else if (streq(argv
[optind
], "dot"))
1479 r
= dot(bus
, argv
+optind
+1);
1480 else if (streq(argv
[optind
], "dump"))
1481 r
= dump(bus
, argv
+optind
+1);
1482 else if (streq(argv
[optind
], "set-log-level"))
1483 r
= set_log_level(bus
, argv
+optind
+1);
1484 else if (streq(argv
[optind
], "set-log-target"))
1485 r
= set_log_target(bus
, argv
+optind
+1);
1487 log_error("Unknown operation '%s'.", argv
[optind
]);
1493 strv_free(arg_dot_from_patterns
);
1494 strv_free(arg_dot_to_patterns
);
1496 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;