2 This file is part of systemd.
4 Copyright 2010-2013 Lennart Poettering
5 Copyright 2013 Simon Peeters
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "alloc-util.h"
29 #include "analyze-verify.h"
30 #include "bus-error.h"
31 #include "bus-unit-util.h"
33 #include "glob-util.h"
35 #include "locale-util.h"
38 #include "parse-util.h"
39 #include "seccomp-util.h"
43 #include "terminal-util.h"
44 #include "unit-name.h"
47 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
48 #define SCALE_Y (20.0)
50 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
52 #define svg(...) printf(__VA_ARGS__)
54 #define svg_bar(class, x1, x2, y) \
55 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
57 SCALE_X * (x1), SCALE_Y * (y), \
58 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
60 #define svg_text(b, x, y, format, ...) \
62 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
63 svg(format, ## __VA_ARGS__); \
72 static char** arg_dot_from_patterns
= NULL
;
73 static char** arg_dot_to_patterns
= NULL
;
74 static usec_t arg_fuzz
= 0;
75 static bool arg_no_pager
= false;
76 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
77 static char *arg_host
= NULL
;
78 static bool arg_user
= false;
79 static bool arg_man
= true;
85 usec_t kernel_done_time
;
87 usec_t userspace_time
;
89 usec_t security_start_time
;
90 usec_t security_finish_time
;
91 usec_t generators_start_time
;
92 usec_t generators_finish_time
;
93 usec_t unitsload_start_time
;
94 usec_t unitsload_finish_time
;
97 * If we're analyzing the user instance, all timestamps will be offset
98 * by its own start-up timestamp, which may be arbitrarily big.
99 * With "plot", this causes arbitrarily wide output SVG files which almost
100 * completely consist of empty space. Thus we cancel out this offset.
102 * This offset is subtracted from times above by acquire_boot_times(),
103 * but it still needs to be subtracted from unit-specific timestamps
104 * (so it is stored here for reference).
106 usec_t reverse_offset
;
121 char *kernel_release
;
122 char *kernel_version
;
123 char *os_pretty_name
;
124 char *virtualization
;
128 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
129 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
138 r
= sd_bus_get_property_trivial(
140 "org.freedesktop.systemd1",
148 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
155 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
156 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
164 r
= sd_bus_get_property_strv(
166 "org.freedesktop.systemd1",
168 "org.freedesktop.systemd1.Unit",
173 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
180 static int compare_unit_time(const void *a
, const void *b
) {
181 return compare(((struct unit_times
*)b
)->time
,
182 ((struct unit_times
*)a
)->time
);
185 static int compare_unit_start(const void *a
, const void *b
) {
186 return compare(((struct unit_times
*)a
)->activating
,
187 ((struct unit_times
*)b
)->activating
);
190 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
191 struct unit_times
*p
;
193 for (p
= t
; p
< t
+ n
; p
++)
199 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
208 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
209 static struct boot_times times
;
210 static bool cached
= false;
215 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
217 if (bus_get_uint64_property(bus
,
218 "/org/freedesktop/systemd1",
219 "org.freedesktop.systemd1.Manager",
220 "FirmwareTimestampMonotonic",
221 ×
.firmware_time
) < 0 ||
222 bus_get_uint64_property(bus
,
223 "/org/freedesktop/systemd1",
224 "org.freedesktop.systemd1.Manager",
225 "LoaderTimestampMonotonic",
226 ×
.loader_time
) < 0 ||
227 bus_get_uint64_property(bus
,
228 "/org/freedesktop/systemd1",
229 "org.freedesktop.systemd1.Manager",
231 ×
.kernel_time
) < 0 ||
232 bus_get_uint64_property(bus
,
233 "/org/freedesktop/systemd1",
234 "org.freedesktop.systemd1.Manager",
235 "InitRDTimestampMonotonic",
236 ×
.initrd_time
) < 0 ||
237 bus_get_uint64_property(bus
,
238 "/org/freedesktop/systemd1",
239 "org.freedesktop.systemd1.Manager",
240 "UserspaceTimestampMonotonic",
241 ×
.userspace_time
) < 0 ||
242 bus_get_uint64_property(bus
,
243 "/org/freedesktop/systemd1",
244 "org.freedesktop.systemd1.Manager",
245 "FinishTimestampMonotonic",
246 ×
.finish_time
) < 0 ||
247 bus_get_uint64_property(bus
,
248 "/org/freedesktop/systemd1",
249 "org.freedesktop.systemd1.Manager",
250 "SecurityStartTimestampMonotonic",
251 ×
.security_start_time
) < 0 ||
252 bus_get_uint64_property(bus
,
253 "/org/freedesktop/systemd1",
254 "org.freedesktop.systemd1.Manager",
255 "SecurityFinishTimestampMonotonic",
256 ×
.security_finish_time
) < 0 ||
257 bus_get_uint64_property(bus
,
258 "/org/freedesktop/systemd1",
259 "org.freedesktop.systemd1.Manager",
260 "GeneratorsStartTimestampMonotonic",
261 ×
.generators_start_time
) < 0 ||
262 bus_get_uint64_property(bus
,
263 "/org/freedesktop/systemd1",
264 "org.freedesktop.systemd1.Manager",
265 "GeneratorsFinishTimestampMonotonic",
266 ×
.generators_finish_time
) < 0 ||
267 bus_get_uint64_property(bus
,
268 "/org/freedesktop/systemd1",
269 "org.freedesktop.systemd1.Manager",
270 "UnitsLoadStartTimestampMonotonic",
271 ×
.unitsload_start_time
) < 0 ||
272 bus_get_uint64_property(bus
,
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "UnitsLoadFinishTimestampMonotonic",
276 ×
.unitsload_finish_time
) < 0)
279 if (times
.finish_time
<= 0) {
280 log_error("Bootup is not yet finished. Please try again later.");
286 * User-instance-specific timestamps processing
287 * (see comment to reverse_offset in struct boot_times).
289 times
.reverse_offset
= times
.userspace_time
;
291 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
292 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
294 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
295 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
297 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
298 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
301 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
303 if (times
.initrd_time
)
304 times
.kernel_done_time
= times
.initrd_time
;
306 times
.kernel_done_time
= times
.userspace_time
;
316 static void free_host_info(struct host_info
*hi
) {
322 free(hi
->kernel_name
);
323 free(hi
->kernel_release
);
324 free(hi
->kernel_version
);
325 free(hi
->os_pretty_name
);
326 free(hi
->virtualization
);
327 free(hi
->architecture
);
331 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
333 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
334 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
335 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
337 struct boot_times
*boot_times
= NULL
;
338 struct unit_times
*unit_times
= NULL
;
342 r
= acquire_boot_times(bus
, &boot_times
);
346 r
= sd_bus_call_method(
348 "org.freedesktop.systemd1",
349 "/org/freedesktop/systemd1",
350 "org.freedesktop.systemd1.Manager",
355 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
359 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
361 bus_log_parse_error(r
);
365 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
366 struct unit_times
*t
;
368 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
376 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
378 if (bus_get_uint64_property(bus
, u
.unit_path
,
379 "org.freedesktop.systemd1.Unit",
380 "InactiveExitTimestampMonotonic",
381 &t
->activating
) < 0 ||
382 bus_get_uint64_property(bus
, u
.unit_path
,
383 "org.freedesktop.systemd1.Unit",
384 "ActiveEnterTimestampMonotonic",
385 &t
->activated
) < 0 ||
386 bus_get_uint64_property(bus
, u
.unit_path
,
387 "org.freedesktop.systemd1.Unit",
388 "ActiveExitTimestampMonotonic",
389 &t
->deactivating
) < 0 ||
390 bus_get_uint64_property(bus
, u
.unit_path
,
391 "org.freedesktop.systemd1.Unit",
392 "InactiveEnterTimestampMonotonic",
393 &t
->deactivated
) < 0) {
398 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
399 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
400 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
401 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
403 if (t
->activated
>= t
->activating
)
404 t
->time
= t
->activated
- t
->activating
;
405 else if (t
->deactivated
>= t
->activating
)
406 t
->time
= t
->deactivated
- t
->activating
;
410 if (t
->activating
== 0)
413 t
->name
= strdup(u
.id
);
414 if (t
->name
== NULL
) {
421 bus_log_parse_error(r
);
430 free_unit_times(unit_times
, (unsigned) c
);
434 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
435 static const struct bus_properties_map hostname_map
[] = {
436 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
437 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
438 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
439 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
440 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
444 static const struct bus_properties_map manager_map
[] = {
445 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
446 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
450 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
451 _cleanup_(free_host_infop
) struct host_info
*host
;
454 host
= new0(struct host_info
, 1);
458 r
= bus_map_all_properties(bus
,
459 "org.freedesktop.hostname1",
460 "/org/freedesktop/hostname1",
464 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
466 r
= bus_map_all_properties(bus
,
467 "org.freedesktop.systemd1",
468 "/org/freedesktop/systemd1",
472 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
480 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
481 char ts
[FORMAT_TIMESPAN_MAX
];
482 struct boot_times
*t
;
483 static char buf
[4096];
488 r
= acquire_boot_times(bus
, &t
);
495 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
496 if (t
->firmware_time
)
497 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
499 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
501 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
502 if (t
->initrd_time
> 0)
503 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
505 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
506 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
516 static void svg_graph_box(double height
, double begin
, double end
) {
519 /* outside box, fill */
520 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
521 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
523 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
524 /* lines for each second */
525 if (i
% 5000000 == 0)
526 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
527 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
528 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
529 else if (i
% 1000000 == 0)
530 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
531 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
532 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
534 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
535 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
539 static int analyze_plot(sd_bus
*bus
) {
540 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
541 struct unit_times
*times
;
542 struct boot_times
*boot
;
545 _cleanup_free_
char *pretty_times
= NULL
;
546 struct unit_times
*u
;
548 n
= acquire_boot_times(bus
, &boot
);
552 n
= pretty_boot_time(bus
, &pretty_times
);
556 n
= acquire_host_info(bus
, &host
);
560 n
= acquire_time_data(bus
, ×
);
564 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
566 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
570 if (boot
->firmware_time
> boot
->loader_time
)
572 if (boot
->loader_time
) {
577 if (boot
->initrd_time
)
579 if (boot
->kernel_time
)
582 for (u
= times
; u
< times
+ n
; u
++) {
583 double text_start
, text_width
;
585 if (u
->activating
< boot
->userspace_time
||
586 u
->activating
> boot
->finish_time
) {
587 u
->name
= mfree(u
->name
);
591 /* If the text cannot fit on the left side then
592 * increase the svg width so it fits on the right.
593 * TODO: calculate the text width more accurately */
594 text_width
= 8.0 * strlen(u
->name
);
595 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
596 if (text_width
> text_start
&& text_width
+ text_start
> width
)
597 width
= text_width
+ text_start
;
599 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
600 && u
->activated
== 0 && u
->deactivating
== 0)
601 u
->activated
= u
->deactivating
= u
->deactivated
;
602 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
603 u
->activated
= boot
->finish_time
;
604 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
605 u
->deactivating
= boot
->finish_time
;
606 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
607 u
->deactivated
= boot
->finish_time
;
611 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
612 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
613 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
615 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
616 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
617 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
618 5 * SCALE_Y
/* legend */);
620 /* write some basic info as a comment, including some help */
621 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
622 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
623 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
624 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
625 "<!-- point your browser to this file. -->\n\n"
626 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
629 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
630 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
631 " rect.background { fill: rgb(255,255,255); }\n"
632 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
633 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
634 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
635 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
636 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
637 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
638 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
639 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
640 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
641 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
642 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
643 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
644 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
646 " line.sec5 { stroke-width: 2; }\n"
647 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
648 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
649 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
650 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
651 " text.sec { font-size: 10px; }\n"
652 " ]]>\n </style>\n</defs>\n\n");
654 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
655 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
656 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
657 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
658 strempty(host
->hostname
),
659 strempty(host
->kernel_name
),
660 strempty(host
->kernel_release
),
661 strempty(host
->kernel_version
),
662 strempty(host
->architecture
),
663 strempty(host
->virtualization
));
665 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
666 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
668 if (boot
->firmware_time
) {
669 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
670 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
673 if (boot
->loader_time
) {
674 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
675 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
678 if (boot
->kernel_time
) {
679 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
680 svg_text(true, 0, y
, "kernel");
683 if (boot
->initrd_time
) {
684 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
685 svg_text(true, boot
->initrd_time
, y
, "initrd");
688 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
689 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
690 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
691 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
692 svg_text(true, boot
->userspace_time
, y
, "systemd");
695 for (u
= times
; u
< times
+ n
; u
++) {
696 char ts
[FORMAT_TIMESPAN_MAX
];
702 svg_bar("activating", u
->activating
, u
->activated
, y
);
703 svg_bar("active", u
->activated
, u
->deactivating
, y
);
704 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
706 /* place the text on the left if we have passed the half of the svg width */
707 b
= u
->activating
* SCALE_X
< width
/ 2;
709 svg_text(b
, u
->activating
, y
, "%s (%s)",
710 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
712 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
719 svg("<g transform=\"translate(20,100)\">\n");
721 svg_bar("activating", 0, 300000, y
);
722 svg_text(true, 400000, y
, "Activating");
724 svg_bar("active", 0, 300000, y
);
725 svg_text(true, 400000, y
, "Active");
727 svg_bar("deactivating", 0, 300000, y
);
728 svg_text(true, 400000, y
, "Deactivating");
730 svg_bar("security", 0, 300000, y
);
731 svg_text(true, 400000, y
, "Setting up security module");
733 svg_bar("generators", 0, 300000, y
);
734 svg_text(true, 400000, y
, "Generators");
736 svg_bar("unitsload", 0, 300000, y
);
737 svg_text(true, 400000, y
, "Loading unit files");
744 free_unit_times(times
, (unsigned) n
);
750 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
751 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
753 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
755 for (i
= level
; i
!= 0; i
--)
756 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
758 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
762 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
763 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
764 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ansi_normal());
765 else if (times
->activated
> boot
->userspace_time
)
766 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
776 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
777 _cleanup_free_
char *path
= NULL
;
783 path
= unit_dbus_path_from_name(name
);
787 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
790 static Hashmap
*unit_times_hashmap
;
792 static int list_dependencies_compare(const void *_a
, const void *_b
) {
793 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
794 usec_t usa
= 0, usb
= 0;
795 struct unit_times
*times
;
797 times
= hashmap_get(unit_times_hashmap
, *a
);
799 usa
= times
->activated
;
800 times
= hashmap_get(unit_times_hashmap
, *b
);
802 usb
= times
->activated
;
807 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
808 unsigned int branches
) {
809 _cleanup_strv_free_
char **deps
= NULL
;
812 usec_t service_longest
= 0;
814 struct unit_times
*times
;
815 struct boot_times
*boot
;
817 if (strv_extend(units
, name
))
820 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
824 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
826 r
= acquire_boot_times(bus
, &boot
);
830 STRV_FOREACH(c
, deps
) {
831 times
= hashmap_get(unit_times_hashmap
, *c
);
834 && times
->activated
<= boot
->finish_time
835 && (times
->activated
>= service_longest
836 || service_longest
== 0)) {
837 service_longest
= times
->activated
;
842 if (service_longest
== 0 )
845 STRV_FOREACH(c
, deps
) {
846 times
= hashmap_get(unit_times_hashmap
, *c
);
847 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
854 STRV_FOREACH(c
, deps
) {
855 times
= hashmap_get(unit_times_hashmap
, *c
);
858 || times
->activated
> boot
->finish_time
859 || service_longest
- times
->activated
> arg_fuzz
)
864 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
868 if (strv_contains(*units
, *c
)) {
869 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
876 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
877 (branches
<< 1) | (to_print
? 1 : 0));
887 static int list_dependencies(sd_bus
*bus
, const char *name
) {
888 _cleanup_strv_free_
char **units
= NULL
;
889 char ts
[FORMAT_TIMESPAN_MAX
];
890 struct unit_times
*times
;
893 _cleanup_free_
char *path
= NULL
;
894 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
895 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
896 struct boot_times
*boot
;
900 path
= unit_dbus_path_from_name(name
);
904 r
= sd_bus_get_property(
906 "org.freedesktop.systemd1",
908 "org.freedesktop.systemd1.Unit",
914 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
918 r
= sd_bus_message_read(reply
, "s", &id
);
920 return bus_log_parse_error(r
);
922 times
= hashmap_get(unit_times_hashmap
, id
);
924 r
= acquire_boot_times(bus
, &boot
);
930 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
931 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ansi_normal());
932 else if (times
->activated
> boot
->userspace_time
)
933 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
938 return list_dependencies_one(bus
, name
, 0, &units
, 0);
941 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
942 struct unit_times
*times
;
947 n
= acquire_time_data(bus
, ×
);
951 h
= hashmap_new(&string_hash_ops
);
955 for (i
= 0; i
< (unsigned)n
; i
++) {
956 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
960 unit_times_hashmap
= h
;
962 pager_open(arg_no_pager
, false);
964 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
965 "The time the unit takes to start is printed after the \"+\" character.\n");
967 if (!strv_isempty(names
)) {
969 STRV_FOREACH(name
, names
)
970 list_dependencies(bus
, *name
);
972 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
975 free_unit_times(times
, (unsigned) n
);
979 static int analyze_blame(sd_bus
*bus
) {
980 struct unit_times
*times
;
984 n
= acquire_time_data(bus
, ×
);
988 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
990 pager_open(arg_no_pager
, false);
992 for (i
= 0; i
< (unsigned) n
; i
++) {
993 char ts
[FORMAT_TIMESPAN_MAX
];
995 if (times
[i
].time
> 0)
996 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
999 free_unit_times(times
, (unsigned) n
);
1003 static int analyze_time(sd_bus
*bus
) {
1004 _cleanup_free_
char *buf
= NULL
;
1007 r
= pretty_boot_time(bus
, &buf
);
1015 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
[]) {
1016 _cleanup_strv_free_
char **units
= NULL
;
1019 bool match_patterns
;
1025 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1027 if (!strv_isempty(from_patterns
) &&
1029 !strv_fnmatch(from_patterns
, u
->id
, 0))
1032 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1036 STRV_FOREACH(unit
, units
) {
1037 bool match_patterns2
;
1039 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1041 if (!strv_isempty(to_patterns
) &&
1043 !strv_fnmatch(to_patterns
, *unit
, 0))
1046 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1049 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1055 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1061 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1062 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1067 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1068 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1071 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1074 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1077 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1085 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1086 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1090 STRV_FOREACH(pattern
, patterns
) {
1091 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1092 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1094 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1097 if (string_is_glob(*pattern
))
1100 unit
= unit_dbus_path_from_name(*pattern
);
1104 r
= sd_bus_get_property_string(
1106 "org.freedesktop.systemd1",
1108 "org.freedesktop.systemd1.Unit",
1113 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1115 if (!streq(*pattern
, unit_id
)) {
1116 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1121 *ret
= expanded_patterns
;
1122 expanded_patterns
= NULL
; /* do not free */
1127 static int dot(sd_bus
*bus
, char* patterns
[]) {
1128 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1129 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1130 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1131 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1132 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1136 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1140 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1144 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1148 r
= sd_bus_call_method(
1150 "org.freedesktop.systemd1",
1151 "/org/freedesktop/systemd1",
1152 "org.freedesktop.systemd1.Manager",
1158 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1162 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1164 return bus_log_parse_error(r
);
1166 printf("digraph systemd {\n");
1168 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1170 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1175 return bus_log_parse_error(r
);
1179 log_info(" Color legend: black = Requires\n"
1180 " dark blue = Requisite\n"
1181 " dark grey = Wants\n"
1182 " red = Conflicts\n"
1183 " green = After\n");
1186 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1187 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1192 static int dump(sd_bus
*bus
, char **args
) {
1193 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1194 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1195 const char *text
= NULL
;
1198 if (!strv_isempty(args
)) {
1199 log_error("Too many arguments.");
1203 pager_open(arg_no_pager
, false);
1205 r
= sd_bus_call_method(
1207 "org.freedesktop.systemd1",
1208 "/org/freedesktop/systemd1",
1209 "org.freedesktop.systemd1.Manager",
1215 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1217 r
= sd_bus_message_read(reply
, "s", &text
);
1219 return bus_log_parse_error(r
);
1221 fputs(text
, stdout
);
1225 static int set_log_level(sd_bus
*bus
, char **args
) {
1226 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1232 if (strv_length(args
) != 1) {
1233 log_error("This command expects one argument only.");
1237 r
= sd_bus_set_property(
1239 "org.freedesktop.systemd1",
1240 "/org/freedesktop/systemd1",
1241 "org.freedesktop.systemd1.Manager",
1247 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1252 static int set_log_target(sd_bus
*bus
, char **args
) {
1253 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1259 if (strv_length(args
) != 1) {
1260 log_error("This command expects one argument only.");
1264 r
= sd_bus_set_property(
1266 "org.freedesktop.systemd1",
1267 "/org/freedesktop/systemd1",
1268 "org.freedesktop.systemd1.Manager",
1274 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1279 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1280 const char *syscall
;
1282 printf("%s\n", set
->name
);
1283 printf(" # %s\n", set
->help
);
1284 NULSTR_FOREACH(syscall
, set
->value
)
1285 printf(" %s\n", syscall
);
1288 static int dump_syscall_filters(char** names
) {
1291 pager_open(arg_no_pager
, false);
1293 if (strv_isempty(names
)) {
1296 for (i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1299 dump_syscall_filter(syscall_filter_sets
+ i
);
1305 STRV_FOREACH(name
, names
) {
1306 const SyscallFilterSet
*set
;
1311 set
= syscall_filter_set_find(*name
);
1313 /* make sure the error appears below normal output */
1316 log_error("Filter set \"%s\" not found.", *name
);
1320 dump_syscall_filter(set
);
1328 static void help(void) {
1330 pager_open(arg_no_pager
, false);
1332 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1333 "Profile systemd, show unit dependencies, check unit files.\n\n"
1334 " -h --help Show this help\n"
1335 " --version Show package version\n"
1336 " --no-pager Do not pipe output into a pager\n"
1337 " --system Operate on system systemd instance\n"
1338 " --user Operate on user systemd instance\n"
1339 " -H --host=[USER@]HOST Operate on remote host\n"
1340 " -M --machine=CONTAINER Operate on local container\n"
1341 " --order Show only order in the graph\n"
1342 " --require Show only requirement in the graph\n"
1343 " --from-pattern=GLOB Show only origins in the graph\n"
1344 " --to-pattern=GLOB Show only destinations in the graph\n"
1345 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1346 " earlier than the latest in the branch\n"
1347 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1349 " time Print time spent in the kernel\n"
1350 " blame Print list of running units ordered by time to init\n"
1351 " critical-chain Print a tree of the time critical chain of units\n"
1352 " plot Output SVG graphic showing service initialization\n"
1353 " dot Output dependency graph in dot(1) format\n"
1354 " set-log-level LEVEL Set logging threshold for manager\n"
1355 " set-log-target TARGET Set logging target for manager\n"
1356 " dump Output state serialization of service manager\n"
1357 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
1358 " verify FILE... Check unit files for correctness\n"
1359 , program_invocation_short_name
);
1361 /* When updating this list, including descriptions, apply
1362 * changes to shell-completion/bash/systemd-analyze and
1363 * shell-completion/zsh/_systemd-analyze too. */
1366 static int parse_argv(int argc
, char *argv
[]) {
1368 ARG_VERSION
= 0x100,
1373 ARG_DOT_FROM_PATTERN
,
1380 static const struct option options
[] = {
1381 { "help", no_argument
, NULL
, 'h' },
1382 { "version", no_argument
, NULL
, ARG_VERSION
},
1383 { "order", no_argument
, NULL
, ARG_ORDER
},
1384 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1385 { "user", no_argument
, NULL
, ARG_USER
},
1386 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1387 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1388 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1389 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1390 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1391 { "man", optional_argument
, NULL
, ARG_MAN
},
1392 { "host", required_argument
, NULL
, 'H' },
1393 { "machine", required_argument
, NULL
, 'M' },
1402 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1421 arg_dot
= DEP_ORDER
;
1425 arg_dot
= DEP_REQUIRE
;
1428 case ARG_DOT_FROM_PATTERN
:
1429 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1434 case ARG_DOT_TO_PATTERN
:
1435 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1441 r
= parse_sec(optarg
, &arg_fuzz
);
1447 arg_no_pager
= true;
1451 arg_transport
= BUS_TRANSPORT_REMOTE
;
1456 arg_transport
= BUS_TRANSPORT_MACHINE
;
1462 r
= parse_boolean(optarg
);
1464 log_error("Failed to parse --man= argument.");
1478 assert_not_reached("Unhandled option code.");
1481 return 1; /* work to do */
1484 int main(int argc
, char *argv
[]) {
1487 setlocale(LC_ALL
, "");
1488 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1489 log_parse_environment();
1492 r
= parse_argv(argc
, argv
);
1496 if (streq_ptr(argv
[optind
], "verify"))
1497 r
= verify_units(argv
+optind
+1,
1498 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1501 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1503 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1505 log_error_errno(r
, "Failed to create bus connection: %m");
1509 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1510 r
= analyze_time(bus
);
1511 else if (streq(argv
[optind
], "blame"))
1512 r
= analyze_blame(bus
);
1513 else if (streq(argv
[optind
], "critical-chain"))
1514 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1515 else if (streq(argv
[optind
], "plot"))
1516 r
= analyze_plot(bus
);
1517 else if (streq(argv
[optind
], "dot"))
1518 r
= dot(bus
, argv
+optind
+1);
1519 else if (streq(argv
[optind
], "dump"))
1520 r
= dump(bus
, argv
+optind
+1);
1521 else if (streq(argv
[optind
], "set-log-level"))
1522 r
= set_log_level(bus
, argv
+optind
+1);
1523 else if (streq(argv
[optind
], "set-log-target"))
1524 r
= set_log_target(bus
, argv
+optind
+1);
1525 else if (streq(argv
[optind
], "syscall-filter"))
1526 r
= dump_syscall_filters(argv
+optind
+1);
1528 log_error("Unknown operation '%s'.", argv
[optind
]);
1534 strv_free(arg_dot_from_patterns
);
1535 strv_free(arg_dot_to_patterns
);
1537 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;