1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010-2013 Lennart Poettering
6 Copyright 2013 Simon Peeters
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
29 #include "alloc-util.h"
30 #include "analyze-verify.h"
31 #include "bus-error.h"
32 #include "bus-unit-util.h"
34 #include "glob-util.h"
36 #include "locale-util.h"
39 #include "parse-util.h"
41 #include "seccomp-util.h"
46 #include "terminal-util.h"
47 #include "unit-name.h"
50 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
51 #define SCALE_Y (20.0)
53 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
55 #define svg(...) printf(__VA_ARGS__)
57 #define svg_bar(class, x1, x2, y) \
58 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
60 SCALE_X * (x1), SCALE_Y * (y), \
61 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
63 #define svg_text(b, x, y, format, ...) \
65 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
66 svg(format, ## __VA_ARGS__); \
75 static char** arg_dot_from_patterns
= NULL
;
76 static char** arg_dot_to_patterns
= NULL
;
77 static usec_t arg_fuzz
= 0;
78 static bool arg_no_pager
= false;
79 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
80 static char *arg_host
= NULL
;
81 static bool arg_user
= false;
82 static bool arg_man
= true;
83 static bool arg_generators
= false;
89 usec_t kernel_done_time
;
91 usec_t userspace_time
;
93 usec_t security_start_time
;
94 usec_t security_finish_time
;
95 usec_t generators_start_time
;
96 usec_t generators_finish_time
;
97 usec_t unitsload_start_time
;
98 usec_t unitsload_finish_time
;
101 * If we're analyzing the user instance, all timestamps will be offset
102 * by its own start-up timestamp, which may be arbitrarily big.
103 * With "plot", this causes arbitrarily wide output SVG files which almost
104 * completely consist of empty space. Thus we cancel out this offset.
106 * This offset is subtracted from times above by acquire_boot_times(),
107 * but it still needs to be subtracted from unit-specific timestamps
108 * (so it is stored here for reference).
110 usec_t reverse_offset
;
125 char *kernel_release
;
126 char *kernel_version
;
127 char *os_pretty_name
;
128 char *virtualization
;
132 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
133 _cleanup_(sd_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_(sd_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
) {
326 free(hi
->kernel_name
);
327 free(hi
->kernel_release
);
328 free(hi
->kernel_version
);
329 free(hi
->os_pretty_name
);
330 free(hi
->virtualization
);
331 free(hi
->architecture
);
335 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
337 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
338 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
339 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
341 struct boot_times
*boot_times
= NULL
;
342 struct unit_times
*unit_times
= NULL
;
346 r
= acquire_boot_times(bus
, &boot_times
);
350 r
= sd_bus_call_method(
352 "org.freedesktop.systemd1",
353 "/org/freedesktop/systemd1",
354 "org.freedesktop.systemd1.Manager",
359 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
363 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
365 bus_log_parse_error(r
);
369 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
370 struct unit_times
*t
;
372 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
380 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
382 if (bus_get_uint64_property(bus
, u
.unit_path
,
383 "org.freedesktop.systemd1.Unit",
384 "InactiveExitTimestampMonotonic",
385 &t
->activating
) < 0 ||
386 bus_get_uint64_property(bus
, u
.unit_path
,
387 "org.freedesktop.systemd1.Unit",
388 "ActiveEnterTimestampMonotonic",
389 &t
->activated
) < 0 ||
390 bus_get_uint64_property(bus
, u
.unit_path
,
391 "org.freedesktop.systemd1.Unit",
392 "ActiveExitTimestampMonotonic",
393 &t
->deactivating
) < 0 ||
394 bus_get_uint64_property(bus
, u
.unit_path
,
395 "org.freedesktop.systemd1.Unit",
396 "InactiveEnterTimestampMonotonic",
397 &t
->deactivated
) < 0) {
402 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
403 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
404 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
405 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
407 if (t
->activated
>= t
->activating
)
408 t
->time
= t
->activated
- t
->activating
;
409 else if (t
->deactivated
>= t
->activating
)
410 t
->time
= t
->deactivated
- t
->activating
;
414 if (t
->activating
== 0)
417 t
->name
= strdup(u
.id
);
418 if (t
->name
== NULL
) {
425 bus_log_parse_error(r
);
434 free_unit_times(unit_times
, (unsigned) c
);
438 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
439 static const struct bus_properties_map hostname_map
[] = {
440 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
441 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
442 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
443 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
444 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
448 static const struct bus_properties_map manager_map
[] = {
449 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
450 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
454 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
455 _cleanup_(free_host_infop
) struct host_info
*host
;
458 host
= new0(struct host_info
, 1);
462 r
= bus_map_all_properties(bus
,
463 "org.freedesktop.hostname1",
464 "/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",
478 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
486 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
487 char ts
[FORMAT_TIMESPAN_MAX
];
488 struct boot_times
*t
;
489 static char buf
[4096];
494 r
= acquire_boot_times(bus
, &t
);
501 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
502 if (t
->firmware_time
)
503 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
505 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
507 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
508 if (t
->initrd_time
> 0)
509 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
511 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
512 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
522 static void svg_graph_box(double height
, double begin
, double end
) {
525 /* outside box, fill */
526 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
527 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
529 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
530 /* lines for each second */
531 if (i
% 5000000 == 0)
532 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
533 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
534 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
535 else if (i
% 1000000 == 0)
536 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
537 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
538 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
540 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
541 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
545 static int analyze_plot(sd_bus
*bus
) {
546 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
547 struct unit_times
*times
;
548 struct boot_times
*boot
;
551 _cleanup_free_
char *pretty_times
= NULL
;
552 struct unit_times
*u
;
554 n
= acquire_boot_times(bus
, &boot
);
558 n
= pretty_boot_time(bus
, &pretty_times
);
562 n
= acquire_host_info(bus
, &host
);
566 n
= acquire_time_data(bus
, ×
);
570 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
572 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
576 if (boot
->firmware_time
> boot
->loader_time
)
578 if (boot
->loader_time
) {
583 if (boot
->initrd_time
)
585 if (boot
->kernel_time
)
588 for (u
= times
; u
< times
+ n
; u
++) {
589 double text_start
, text_width
;
591 if (u
->activating
< boot
->userspace_time
||
592 u
->activating
> boot
->finish_time
) {
593 u
->name
= mfree(u
->name
);
597 /* If the text cannot fit on the left side then
598 * increase the svg width so it fits on the right.
599 * TODO: calculate the text width more accurately */
600 text_width
= 8.0 * strlen(u
->name
);
601 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
602 if (text_width
> text_start
&& text_width
+ text_start
> width
)
603 width
= text_width
+ text_start
;
605 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
606 && u
->activated
== 0 && u
->deactivating
== 0)
607 u
->activated
= u
->deactivating
= u
->deactivated
;
608 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
609 u
->activated
= boot
->finish_time
;
610 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
611 u
->deactivating
= boot
->finish_time
;
612 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
613 u
->deactivated
= boot
->finish_time
;
617 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
618 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
619 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
621 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
622 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
623 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
624 5 * SCALE_Y
/* legend */);
626 /* write some basic info as a comment, including some help */
627 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
628 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
629 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
630 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
631 "<!-- point your browser to this file. -->\n\n"
632 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", PACKAGE_VERSION
);
635 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
636 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
637 " rect.background { fill: rgb(255,255,255); }\n"
638 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
639 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
640 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
641 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
643 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
644 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
645 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
646 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
647 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
648 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
649 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
650 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
652 " line.sec5 { stroke-width: 2; }\n"
653 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
654 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
655 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
656 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
657 " text.sec { font-size: 10px; }\n"
658 " ]]>\n </style>\n</defs>\n\n");
660 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
661 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
662 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
663 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
664 strempty(host
->hostname
),
665 strempty(host
->kernel_name
),
666 strempty(host
->kernel_release
),
667 strempty(host
->kernel_version
),
668 strempty(host
->architecture
),
669 strempty(host
->virtualization
));
671 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
672 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
674 if (boot
->firmware_time
) {
675 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
676 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
679 if (boot
->loader_time
) {
680 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
681 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
684 if (boot
->kernel_time
) {
685 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
686 svg_text(true, 0, y
, "kernel");
689 if (boot
->initrd_time
) {
690 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
691 svg_text(true, boot
->initrd_time
, y
, "initrd");
694 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
695 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
696 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
697 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
698 svg_text(true, boot
->userspace_time
, y
, "systemd");
701 for (u
= times
; u
< times
+ n
; u
++) {
702 char ts
[FORMAT_TIMESPAN_MAX
];
708 svg_bar("activating", u
->activating
, u
->activated
, y
);
709 svg_bar("active", u
->activated
, u
->deactivating
, y
);
710 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
712 /* place the text on the left if we have passed the half of the svg width */
713 b
= u
->activating
* SCALE_X
< width
/ 2;
715 svg_text(b
, u
->activating
, y
, "%s (%s)",
716 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
718 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
725 svg("<g transform=\"translate(20,100)\">\n");
727 svg_bar("activating", 0, 300000, y
);
728 svg_text(true, 400000, y
, "Activating");
730 svg_bar("active", 0, 300000, y
);
731 svg_text(true, 400000, y
, "Active");
733 svg_bar("deactivating", 0, 300000, y
);
734 svg_text(true, 400000, y
, "Deactivating");
736 svg_bar("security", 0, 300000, y
);
737 svg_text(true, 400000, y
, "Setting up security module");
739 svg_bar("generators", 0, 300000, y
);
740 svg_text(true, 400000, y
, "Generators");
742 svg_bar("unitsload", 0, 300000, y
);
743 svg_text(true, 400000, y
, "Loading unit files");
750 free_unit_times(times
, (unsigned) n
);
756 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
757 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
759 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
761 for (i
= level
; i
!= 0; i
--)
762 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
764 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
768 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
769 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
770 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ansi_normal());
771 else if (times
->activated
> boot
->userspace_time
)
772 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
782 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
783 _cleanup_free_
char *path
= NULL
;
789 path
= unit_dbus_path_from_name(name
);
793 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
796 static Hashmap
*unit_times_hashmap
;
798 static int list_dependencies_compare(const void *_a
, const void *_b
) {
799 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
800 usec_t usa
= 0, usb
= 0;
801 struct unit_times
*times
;
803 times
= hashmap_get(unit_times_hashmap
, *a
);
805 usa
= times
->activated
;
806 times
= hashmap_get(unit_times_hashmap
, *b
);
808 usb
= times
->activated
;
813 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
814 unsigned int branches
) {
815 _cleanup_strv_free_
char **deps
= NULL
;
818 usec_t service_longest
= 0;
820 struct unit_times
*times
;
821 struct boot_times
*boot
;
823 if (strv_extend(units
, name
))
826 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
830 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
832 r
= acquire_boot_times(bus
, &boot
);
836 STRV_FOREACH(c
, deps
) {
837 times
= hashmap_get(unit_times_hashmap
, *c
);
840 && times
->activated
<= boot
->finish_time
841 && (times
->activated
>= service_longest
842 || service_longest
== 0)) {
843 service_longest
= times
->activated
;
848 if (service_longest
== 0 )
851 STRV_FOREACH(c
, deps
) {
852 times
= hashmap_get(unit_times_hashmap
, *c
);
853 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
860 STRV_FOREACH(c
, deps
) {
861 times
= hashmap_get(unit_times_hashmap
, *c
);
864 || times
->activated
> boot
->finish_time
865 || service_longest
- times
->activated
> arg_fuzz
)
870 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
874 if (strv_contains(*units
, *c
)) {
875 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
882 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
883 (branches
<< 1) | (to_print
? 1 : 0));
893 static int list_dependencies(sd_bus
*bus
, const char *name
) {
894 _cleanup_strv_free_
char **units
= NULL
;
895 char ts
[FORMAT_TIMESPAN_MAX
];
896 struct unit_times
*times
;
899 _cleanup_free_
char *path
= NULL
;
900 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
901 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
902 struct boot_times
*boot
;
906 path
= unit_dbus_path_from_name(name
);
910 r
= sd_bus_get_property(
912 "org.freedesktop.systemd1",
914 "org.freedesktop.systemd1.Unit",
920 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
924 r
= sd_bus_message_read(reply
, "s", &id
);
926 return bus_log_parse_error(r
);
928 times
= hashmap_get(unit_times_hashmap
, id
);
930 r
= acquire_boot_times(bus
, &boot
);
936 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
937 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ansi_normal());
938 else if (times
->activated
> boot
->userspace_time
)
939 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
944 return list_dependencies_one(bus
, name
, 0, &units
, 0);
947 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
948 struct unit_times
*times
;
953 n
= acquire_time_data(bus
, ×
);
957 h
= hashmap_new(&string_hash_ops
);
961 for (i
= 0; i
< (unsigned)n
; i
++) {
962 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
966 unit_times_hashmap
= h
;
968 pager_open(arg_no_pager
, false);
970 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
971 "The time the unit takes to start is printed after the \"+\" character.\n");
973 if (!strv_isempty(names
)) {
975 STRV_FOREACH(name
, names
)
976 list_dependencies(bus
, *name
);
978 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
981 free_unit_times(times
, (unsigned) n
);
985 static int analyze_blame(sd_bus
*bus
) {
986 struct unit_times
*times
;
990 n
= acquire_time_data(bus
, ×
);
994 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
996 pager_open(arg_no_pager
, false);
998 for (i
= 0; i
< (unsigned) n
; i
++) {
999 char ts
[FORMAT_TIMESPAN_MAX
];
1001 if (times
[i
].time
> 0)
1002 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1005 free_unit_times(times
, (unsigned) n
);
1009 static int analyze_time(sd_bus
*bus
) {
1010 _cleanup_free_
char *buf
= NULL
;
1013 r
= pretty_boot_time(bus
, &buf
);
1021 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
[]) {
1022 _cleanup_strv_free_
char **units
= NULL
;
1025 bool match_patterns
;
1031 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1033 if (!strv_isempty(from_patterns
) &&
1035 !strv_fnmatch(from_patterns
, u
->id
, 0))
1038 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1042 STRV_FOREACH(unit
, units
) {
1043 bool match_patterns2
;
1045 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1047 if (!strv_isempty(to_patterns
) &&
1049 !strv_fnmatch(to_patterns
, *unit
, 0))
1052 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1055 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1061 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1067 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1068 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1073 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1074 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1077 r
= graph_one_property(bus
, u
, "Requisite", "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
);
1091 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1092 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1096 STRV_FOREACH(pattern
, patterns
) {
1097 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1098 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1100 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1103 if (string_is_glob(*pattern
))
1106 unit
= unit_dbus_path_from_name(*pattern
);
1110 r
= sd_bus_get_property_string(
1112 "org.freedesktop.systemd1",
1114 "org.freedesktop.systemd1.Unit",
1119 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1121 if (!streq(*pattern
, unit_id
)) {
1122 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1127 *ret
= expanded_patterns
;
1128 expanded_patterns
= NULL
; /* do not free */
1133 static int dot(sd_bus
*bus
, char* patterns
[]) {
1134 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1135 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1136 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1137 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1138 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1142 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1146 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1150 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1154 r
= sd_bus_call_method(
1156 "org.freedesktop.systemd1",
1157 "/org/freedesktop/systemd1",
1158 "org.freedesktop.systemd1.Manager",
1164 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1168 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1170 return bus_log_parse_error(r
);
1172 printf("digraph systemd {\n");
1174 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1176 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1181 return bus_log_parse_error(r
);
1185 log_info(" Color legend: black = Requires\n"
1186 " dark blue = Requisite\n"
1187 " dark grey = Wants\n"
1188 " red = Conflicts\n"
1189 " green = After\n");
1192 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1193 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1198 static int dump(sd_bus
*bus
, char **args
) {
1199 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1200 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1201 const char *text
= NULL
;
1204 if (!strv_isempty(args
)) {
1205 log_error("Too many arguments.");
1209 pager_open(arg_no_pager
, false);
1211 r
= sd_bus_call_method(
1213 "org.freedesktop.systemd1",
1214 "/org/freedesktop/systemd1",
1215 "org.freedesktop.systemd1.Manager",
1221 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1223 r
= sd_bus_message_read(reply
, "s", &text
);
1225 return bus_log_parse_error(r
);
1227 fputs(text
, stdout
);
1231 static int set_log_level(sd_bus
*bus
, char **args
) {
1232 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1238 if (strv_length(args
) != 1) {
1239 log_error("This command expects one argument only.");
1243 r
= sd_bus_set_property(
1245 "org.freedesktop.systemd1",
1246 "/org/freedesktop/systemd1",
1247 "org.freedesktop.systemd1.Manager",
1253 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1258 static int get_log_level(sd_bus
*bus
, char **args
) {
1259 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1261 _cleanup_free_
char *level
= NULL
;
1266 if (!strv_isempty(args
)) {
1267 log_error("Too many arguments.");
1271 r
= sd_bus_get_property_string(
1273 "org.freedesktop.systemd1",
1274 "/org/freedesktop/systemd1",
1275 "org.freedesktop.systemd1.Manager",
1280 return log_error_errno(r
, "Failed to get log level: %s", bus_error_message(&error
, r
));
1286 static int set_log_target(sd_bus
*bus
, char **args
) {
1287 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1293 if (strv_length(args
) != 1) {
1294 log_error("This command expects one argument only.");
1298 r
= sd_bus_set_property(
1300 "org.freedesktop.systemd1",
1301 "/org/freedesktop/systemd1",
1302 "org.freedesktop.systemd1.Manager",
1308 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1313 static int get_log_target(sd_bus
*bus
, char **args
) {
1314 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1316 _cleanup_free_
char *target
= NULL
;
1321 if (!strv_isempty(args
)) {
1322 log_error("Too many arguments.");
1326 r
= sd_bus_get_property_string(
1328 "org.freedesktop.systemd1",
1329 "/org/freedesktop/systemd1",
1330 "org.freedesktop.systemd1.Manager",
1335 return log_error_errno(r
, "Failed to get log target: %s", bus_error_message(&error
, r
));
1342 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1343 const char *syscall
;
1345 printf("%s\n", set
->name
);
1346 printf(" # %s\n", set
->help
);
1347 NULSTR_FOREACH(syscall
, set
->value
)
1348 printf(" %s\n", syscall
);
1351 static int dump_syscall_filters(char** names
) {
1354 pager_open(arg_no_pager
, false);
1356 if (strv_isempty(names
)) {
1359 for (i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1362 dump_syscall_filter(syscall_filter_sets
+ i
);
1368 STRV_FOREACH(name
, names
) {
1369 const SyscallFilterSet
*set
;
1374 set
= syscall_filter_set_find(*name
);
1376 /* make sure the error appears below normal output */
1379 log_error("Filter set \"%s\" not found.", *name
);
1383 dump_syscall_filter(set
);
1392 static int dump_syscall_filters(char** names
) {
1393 log_error("Not compiled with syscall filters, sorry.");
1398 static void help(void) {
1400 pager_open(arg_no_pager
, false);
1402 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1403 "Profile systemd, show unit dependencies, check unit files.\n\n"
1404 " -h --help Show this help\n"
1405 " --version Show package version\n"
1406 " --no-pager Do not pipe output into a pager\n"
1407 " --system Operate on system systemd instance\n"
1408 " --user Operate on user systemd instance\n"
1409 " -H --host=[USER@]HOST Operate on remote host\n"
1410 " -M --machine=CONTAINER Operate on local container\n"
1411 " --order Show only order in the graph\n"
1412 " --require Show only requirement in the graph\n"
1413 " --from-pattern=GLOB Show only origins in the graph\n"
1414 " --to-pattern=GLOB Show only destinations in the graph\n"
1415 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1416 " earlier than the latest in the branch\n"
1417 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1418 " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n\n"
1420 " time Print time spent in the kernel\n"
1421 " blame Print list of running units ordered by time to init\n"
1422 " critical-chain Print a tree of the time critical chain of units\n"
1423 " plot Output SVG graphic showing service initialization\n"
1424 " dot Output dependency graph in man:dot(1) format\n"
1425 " set-log-level LEVEL Set logging threshold for manager\n"
1426 " set-log-target TARGET Set logging target for manager\n"
1427 " get-log-level Get logging threshold for manager\n"
1428 " get-log-target Get logging target for manager\n"
1429 " dump Output state serialization of service manager\n"
1430 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
1431 " verify FILE... Check unit files for correctness\n"
1432 , program_invocation_short_name
);
1434 /* When updating this list, including descriptions, apply
1435 * changes to shell-completion/bash/systemd-analyze and
1436 * shell-completion/zsh/_systemd-analyze too. */
1439 static int parse_argv(int argc
, char *argv
[]) {
1441 ARG_VERSION
= 0x100,
1446 ARG_DOT_FROM_PATTERN
,
1454 static const struct option options
[] = {
1455 { "help", no_argument
, NULL
, 'h' },
1456 { "version", no_argument
, NULL
, ARG_VERSION
},
1457 { "order", no_argument
, NULL
, ARG_ORDER
},
1458 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1459 { "user", no_argument
, NULL
, ARG_USER
},
1460 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1461 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1462 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1463 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1464 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1465 { "man", optional_argument
, NULL
, ARG_MAN
},
1466 { "generators", optional_argument
, NULL
, ARG_GENERATORS
},
1467 { "host", required_argument
, NULL
, 'H' },
1468 { "machine", required_argument
, NULL
, 'M' },
1477 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1496 arg_dot
= DEP_ORDER
;
1500 arg_dot
= DEP_REQUIRE
;
1503 case ARG_DOT_FROM_PATTERN
:
1504 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1509 case ARG_DOT_TO_PATTERN
:
1510 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1516 r
= parse_sec(optarg
, &arg_fuzz
);
1522 arg_no_pager
= true;
1526 arg_transport
= BUS_TRANSPORT_REMOTE
;
1531 arg_transport
= BUS_TRANSPORT_MACHINE
;
1537 r
= parse_boolean(optarg
);
1539 log_error("Failed to parse --man= argument.");
1549 case ARG_GENERATORS
:
1551 r
= parse_boolean(optarg
);
1553 log_error("Failed to parse --generators= argument.");
1557 arg_generators
= !!r
;
1559 arg_generators
= true;
1567 assert_not_reached("Unhandled option code.");
1570 return 1; /* work to do */
1573 int main(int argc
, char *argv
[]) {
1576 setlocale(LC_ALL
, "");
1577 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1578 log_parse_environment();
1581 r
= parse_argv(argc
, argv
);
1585 if (streq_ptr(argv
[optind
], "verify"))
1586 r
= verify_units(argv
+optind
+1,
1587 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1591 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1593 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1595 log_error_errno(r
, "Failed to create bus connection: %m");
1599 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1600 r
= analyze_time(bus
);
1601 else if (streq(argv
[optind
], "blame"))
1602 r
= analyze_blame(bus
);
1603 else if (streq(argv
[optind
], "critical-chain"))
1604 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1605 else if (streq(argv
[optind
], "plot"))
1606 r
= analyze_plot(bus
);
1607 else if (streq(argv
[optind
], "dot"))
1608 r
= dot(bus
, argv
+optind
+1);
1609 else if (streq(argv
[optind
], "dump"))
1610 r
= dump(bus
, argv
+optind
+1);
1611 else if (streq(argv
[optind
], "set-log-level"))
1612 r
= set_log_level(bus
, argv
+optind
+1);
1613 else if (streq(argv
[optind
], "get-log-level"))
1614 r
= get_log_level(bus
, argv
+optind
+1);
1615 else if (streq(argv
[optind
], "set-log-target"))
1616 r
= set_log_target(bus
, argv
+optind
+1);
1617 else if (streq(argv
[optind
], "get-log-target"))
1618 r
= get_log_target(bus
, argv
+optind
+1);
1619 else if (streq(argv
[optind
], "syscall-filter"))
1620 r
= dump_syscall_filters(argv
+optind
+1);
1622 log_error("Unknown operation '%s'.", argv
[optind
]);
1628 strv_free(arg_dot_from_patterns
);
1629 strv_free(arg_dot_to_patterns
);
1631 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;