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"
40 #include "seccomp-util.h"
45 #include "terminal-util.h"
46 #include "unit-name.h"
49 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
50 #define SCALE_Y (20.0)
52 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
54 #define svg(...) printf(__VA_ARGS__)
56 #define svg_bar(class, x1, x2, y) \
57 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
59 SCALE_X * (x1), SCALE_Y * (y), \
60 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
62 #define svg_text(b, x, y, format, ...) \
64 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
65 svg(format, ## __VA_ARGS__); \
74 static char** arg_dot_from_patterns
= NULL
;
75 static char** arg_dot_to_patterns
= NULL
;
76 static usec_t arg_fuzz
= 0;
77 static bool arg_no_pager
= false;
78 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
79 static char *arg_host
= NULL
;
80 static bool arg_user
= false;
81 static bool arg_man
= true;
87 usec_t kernel_done_time
;
89 usec_t userspace_time
;
91 usec_t security_start_time
;
92 usec_t security_finish_time
;
93 usec_t generators_start_time
;
94 usec_t generators_finish_time
;
95 usec_t unitsload_start_time
;
96 usec_t unitsload_finish_time
;
99 * If we're analyzing the user instance, all timestamps will be offset
100 * by its own start-up timestamp, which may be arbitrarily big.
101 * With "plot", this causes arbitrarily wide output SVG files which almost
102 * completely consist of empty space. Thus we cancel out this offset.
104 * This offset is subtracted from times above by acquire_boot_times(),
105 * but it still needs to be subtracted from unit-specific timestamps
106 * (so it is stored here for reference).
108 usec_t reverse_offset
;
123 char *kernel_release
;
124 char *kernel_version
;
125 char *os_pretty_name
;
126 char *virtualization
;
130 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
131 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
140 r
= sd_bus_get_property_trivial(
142 "org.freedesktop.systemd1",
150 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
157 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
158 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
166 r
= sd_bus_get_property_strv(
168 "org.freedesktop.systemd1",
170 "org.freedesktop.systemd1.Unit",
175 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
182 static int compare_unit_time(const void *a
, const void *b
) {
183 return compare(((struct unit_times
*)b
)->time
,
184 ((struct unit_times
*)a
)->time
);
187 static int compare_unit_start(const void *a
, const void *b
) {
188 return compare(((struct unit_times
*)a
)->activating
,
189 ((struct unit_times
*)b
)->activating
);
192 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
193 struct unit_times
*p
;
195 for (p
= t
; p
< t
+ n
; p
++)
201 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
210 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
211 static struct boot_times times
;
212 static bool cached
= false;
217 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
219 if (bus_get_uint64_property(bus
,
220 "/org/freedesktop/systemd1",
221 "org.freedesktop.systemd1.Manager",
222 "FirmwareTimestampMonotonic",
223 ×
.firmware_time
) < 0 ||
224 bus_get_uint64_property(bus
,
225 "/org/freedesktop/systemd1",
226 "org.freedesktop.systemd1.Manager",
227 "LoaderTimestampMonotonic",
228 ×
.loader_time
) < 0 ||
229 bus_get_uint64_property(bus
,
230 "/org/freedesktop/systemd1",
231 "org.freedesktop.systemd1.Manager",
233 ×
.kernel_time
) < 0 ||
234 bus_get_uint64_property(bus
,
235 "/org/freedesktop/systemd1",
236 "org.freedesktop.systemd1.Manager",
237 "InitRDTimestampMonotonic",
238 ×
.initrd_time
) < 0 ||
239 bus_get_uint64_property(bus
,
240 "/org/freedesktop/systemd1",
241 "org.freedesktop.systemd1.Manager",
242 "UserspaceTimestampMonotonic",
243 ×
.userspace_time
) < 0 ||
244 bus_get_uint64_property(bus
,
245 "/org/freedesktop/systemd1",
246 "org.freedesktop.systemd1.Manager",
247 "FinishTimestampMonotonic",
248 ×
.finish_time
) < 0 ||
249 bus_get_uint64_property(bus
,
250 "/org/freedesktop/systemd1",
251 "org.freedesktop.systemd1.Manager",
252 "SecurityStartTimestampMonotonic",
253 ×
.security_start_time
) < 0 ||
254 bus_get_uint64_property(bus
,
255 "/org/freedesktop/systemd1",
256 "org.freedesktop.systemd1.Manager",
257 "SecurityFinishTimestampMonotonic",
258 ×
.security_finish_time
) < 0 ||
259 bus_get_uint64_property(bus
,
260 "/org/freedesktop/systemd1",
261 "org.freedesktop.systemd1.Manager",
262 "GeneratorsStartTimestampMonotonic",
263 ×
.generators_start_time
) < 0 ||
264 bus_get_uint64_property(bus
,
265 "/org/freedesktop/systemd1",
266 "org.freedesktop.systemd1.Manager",
267 "GeneratorsFinishTimestampMonotonic",
268 ×
.generators_finish_time
) < 0 ||
269 bus_get_uint64_property(bus
,
270 "/org/freedesktop/systemd1",
271 "org.freedesktop.systemd1.Manager",
272 "UnitsLoadStartTimestampMonotonic",
273 ×
.unitsload_start_time
) < 0 ||
274 bus_get_uint64_property(bus
,
275 "/org/freedesktop/systemd1",
276 "org.freedesktop.systemd1.Manager",
277 "UnitsLoadFinishTimestampMonotonic",
278 ×
.unitsload_finish_time
) < 0)
281 if (times
.finish_time
<= 0) {
282 log_error("Bootup is not yet finished. Please try again later.");
288 * User-instance-specific timestamps processing
289 * (see comment to reverse_offset in struct boot_times).
291 times
.reverse_offset
= times
.userspace_time
;
293 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
294 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
296 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
297 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
302 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
303 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
305 if (times
.initrd_time
)
306 times
.kernel_done_time
= times
.initrd_time
;
308 times
.kernel_done_time
= times
.userspace_time
;
318 static void free_host_info(struct host_info
*hi
) {
324 free(hi
->kernel_name
);
325 free(hi
->kernel_release
);
326 free(hi
->kernel_version
);
327 free(hi
->os_pretty_name
);
328 free(hi
->virtualization
);
329 free(hi
->architecture
);
333 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
335 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
336 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
337 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
339 struct boot_times
*boot_times
= NULL
;
340 struct unit_times
*unit_times
= NULL
;
344 r
= acquire_boot_times(bus
, &boot_times
);
348 r
= sd_bus_call_method(
350 "org.freedesktop.systemd1",
351 "/org/freedesktop/systemd1",
352 "org.freedesktop.systemd1.Manager",
357 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
361 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
363 bus_log_parse_error(r
);
367 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
368 struct unit_times
*t
;
370 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
378 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
380 if (bus_get_uint64_property(bus
, u
.unit_path
,
381 "org.freedesktop.systemd1.Unit",
382 "InactiveExitTimestampMonotonic",
383 &t
->activating
) < 0 ||
384 bus_get_uint64_property(bus
, u
.unit_path
,
385 "org.freedesktop.systemd1.Unit",
386 "ActiveEnterTimestampMonotonic",
387 &t
->activated
) < 0 ||
388 bus_get_uint64_property(bus
, u
.unit_path
,
389 "org.freedesktop.systemd1.Unit",
390 "ActiveExitTimestampMonotonic",
391 &t
->deactivating
) < 0 ||
392 bus_get_uint64_property(bus
, u
.unit_path
,
393 "org.freedesktop.systemd1.Unit",
394 "InactiveEnterTimestampMonotonic",
395 &t
->deactivated
) < 0) {
400 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
401 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
402 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
403 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
405 if (t
->activated
>= t
->activating
)
406 t
->time
= t
->activated
- t
->activating
;
407 else if (t
->deactivated
>= t
->activating
)
408 t
->time
= t
->deactivated
- t
->activating
;
412 if (t
->activating
== 0)
415 t
->name
= strdup(u
.id
);
416 if (t
->name
== NULL
) {
423 bus_log_parse_error(r
);
432 free_unit_times(unit_times
, (unsigned) c
);
436 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
437 static const struct bus_properties_map hostname_map
[] = {
438 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
439 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
440 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
441 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
442 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
446 static const struct bus_properties_map manager_map
[] = {
447 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
448 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
452 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
453 _cleanup_(free_host_infop
) struct host_info
*host
;
456 host
= new0(struct host_info
, 1);
460 r
= bus_map_all_properties(bus
,
461 "org.freedesktop.hostname1",
462 "/org/freedesktop/hostname1",
466 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
468 r
= bus_map_all_properties(bus
,
469 "org.freedesktop.systemd1",
470 "/org/freedesktop/systemd1",
474 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
482 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
483 char ts
[FORMAT_TIMESPAN_MAX
];
484 struct boot_times
*t
;
485 static char buf
[4096];
490 r
= acquire_boot_times(bus
, &t
);
497 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
498 if (t
->firmware_time
)
499 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
501 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
503 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
504 if (t
->initrd_time
> 0)
505 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
507 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
508 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
518 static void svg_graph_box(double height
, double begin
, double end
) {
521 /* outside box, fill */
522 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
523 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
525 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
526 /* lines for each second */
527 if (i
% 5000000 == 0)
528 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
529 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
530 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
531 else if (i
% 1000000 == 0)
532 svg(" <line class=\"sec1\" 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
);
536 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
537 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
541 static int analyze_plot(sd_bus
*bus
) {
542 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
543 struct unit_times
*times
;
544 struct boot_times
*boot
;
547 _cleanup_free_
char *pretty_times
= NULL
;
548 struct unit_times
*u
;
550 n
= acquire_boot_times(bus
, &boot
);
554 n
= pretty_boot_time(bus
, &pretty_times
);
558 n
= acquire_host_info(bus
, &host
);
562 n
= acquire_time_data(bus
, ×
);
566 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
568 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
572 if (boot
->firmware_time
> boot
->loader_time
)
574 if (boot
->loader_time
) {
579 if (boot
->initrd_time
)
581 if (boot
->kernel_time
)
584 for (u
= times
; u
< times
+ n
; u
++) {
585 double text_start
, text_width
;
587 if (u
->activating
< boot
->userspace_time
||
588 u
->activating
> boot
->finish_time
) {
589 u
->name
= mfree(u
->name
);
593 /* If the text cannot fit on the left side then
594 * increase the svg width so it fits on the right.
595 * TODO: calculate the text width more accurately */
596 text_width
= 8.0 * strlen(u
->name
);
597 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
598 if (text_width
> text_start
&& text_width
+ text_start
> width
)
599 width
= text_width
+ text_start
;
601 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
602 && u
->activated
== 0 && u
->deactivating
== 0)
603 u
->activated
= u
->deactivating
= u
->deactivated
;
604 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
605 u
->activated
= boot
->finish_time
;
606 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
607 u
->deactivating
= boot
->finish_time
;
608 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
609 u
->deactivated
= boot
->finish_time
;
613 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
614 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
615 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
617 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
618 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
619 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
620 5 * SCALE_Y
/* legend */);
622 /* write some basic info as a comment, including some help */
623 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
624 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
625 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
626 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
627 "<!-- point your browser to this file. -->\n\n"
628 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
631 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
632 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
633 " rect.background { fill: rgb(255,255,255); }\n"
634 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
635 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
636 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
637 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
638 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
639 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
640 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
641 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
643 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
644 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
645 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
646 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
648 " line.sec5 { stroke-width: 2; }\n"
649 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
650 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
651 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
652 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
653 " text.sec { font-size: 10px; }\n"
654 " ]]>\n </style>\n</defs>\n\n");
656 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
657 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
658 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
659 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
660 strempty(host
->hostname
),
661 strempty(host
->kernel_name
),
662 strempty(host
->kernel_release
),
663 strempty(host
->kernel_version
),
664 strempty(host
->architecture
),
665 strempty(host
->virtualization
));
667 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
668 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
670 if (boot
->firmware_time
) {
671 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
672 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
675 if (boot
->loader_time
) {
676 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
677 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
680 if (boot
->kernel_time
) {
681 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
682 svg_text(true, 0, y
, "kernel");
685 if (boot
->initrd_time
) {
686 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
687 svg_text(true, boot
->initrd_time
, y
, "initrd");
690 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
691 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
692 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
693 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
694 svg_text(true, boot
->userspace_time
, y
, "systemd");
697 for (u
= times
; u
< times
+ n
; u
++) {
698 char ts
[FORMAT_TIMESPAN_MAX
];
704 svg_bar("activating", u
->activating
, u
->activated
, y
);
705 svg_bar("active", u
->activated
, u
->deactivating
, y
);
706 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
708 /* place the text on the left if we have passed the half of the svg width */
709 b
= u
->activating
* SCALE_X
< width
/ 2;
711 svg_text(b
, u
->activating
, y
, "%s (%s)",
712 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
714 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
721 svg("<g transform=\"translate(20,100)\">\n");
723 svg_bar("activating", 0, 300000, y
);
724 svg_text(true, 400000, y
, "Activating");
726 svg_bar("active", 0, 300000, y
);
727 svg_text(true, 400000, y
, "Active");
729 svg_bar("deactivating", 0, 300000, y
);
730 svg_text(true, 400000, y
, "Deactivating");
732 svg_bar("security", 0, 300000, y
);
733 svg_text(true, 400000, y
, "Setting up security module");
735 svg_bar("generators", 0, 300000, y
);
736 svg_text(true, 400000, y
, "Generators");
738 svg_bar("unitsload", 0, 300000, y
);
739 svg_text(true, 400000, y
, "Loading unit files");
746 free_unit_times(times
, (unsigned) n
);
752 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
753 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
755 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
757 for (i
= level
; i
!= 0; i
--)
758 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
760 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
764 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
765 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
766 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ansi_normal());
767 else if (times
->activated
> boot
->userspace_time
)
768 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
778 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
779 _cleanup_free_
char *path
= NULL
;
785 path
= unit_dbus_path_from_name(name
);
789 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
792 static Hashmap
*unit_times_hashmap
;
794 static int list_dependencies_compare(const void *_a
, const void *_b
) {
795 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
796 usec_t usa
= 0, usb
= 0;
797 struct unit_times
*times
;
799 times
= hashmap_get(unit_times_hashmap
, *a
);
801 usa
= times
->activated
;
802 times
= hashmap_get(unit_times_hashmap
, *b
);
804 usb
= times
->activated
;
809 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
810 unsigned int branches
) {
811 _cleanup_strv_free_
char **deps
= NULL
;
814 usec_t service_longest
= 0;
816 struct unit_times
*times
;
817 struct boot_times
*boot
;
819 if (strv_extend(units
, name
))
822 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
826 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
828 r
= acquire_boot_times(bus
, &boot
);
832 STRV_FOREACH(c
, deps
) {
833 times
= hashmap_get(unit_times_hashmap
, *c
);
836 && times
->activated
<= boot
->finish_time
837 && (times
->activated
>= service_longest
838 || service_longest
== 0)) {
839 service_longest
= times
->activated
;
844 if (service_longest
== 0 )
847 STRV_FOREACH(c
, deps
) {
848 times
= hashmap_get(unit_times_hashmap
, *c
);
849 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
856 STRV_FOREACH(c
, deps
) {
857 times
= hashmap_get(unit_times_hashmap
, *c
);
860 || times
->activated
> boot
->finish_time
861 || service_longest
- times
->activated
> arg_fuzz
)
866 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
870 if (strv_contains(*units
, *c
)) {
871 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
878 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
879 (branches
<< 1) | (to_print
? 1 : 0));
889 static int list_dependencies(sd_bus
*bus
, const char *name
) {
890 _cleanup_strv_free_
char **units
= NULL
;
891 char ts
[FORMAT_TIMESPAN_MAX
];
892 struct unit_times
*times
;
895 _cleanup_free_
char *path
= NULL
;
896 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
897 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
898 struct boot_times
*boot
;
902 path
= unit_dbus_path_from_name(name
);
906 r
= sd_bus_get_property(
908 "org.freedesktop.systemd1",
910 "org.freedesktop.systemd1.Unit",
916 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
920 r
= sd_bus_message_read(reply
, "s", &id
);
922 return bus_log_parse_error(r
);
924 times
= hashmap_get(unit_times_hashmap
, id
);
926 r
= acquire_boot_times(bus
, &boot
);
932 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
933 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ansi_normal());
934 else if (times
->activated
> boot
->userspace_time
)
935 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
940 return list_dependencies_one(bus
, name
, 0, &units
, 0);
943 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
944 struct unit_times
*times
;
949 n
= acquire_time_data(bus
, ×
);
953 h
= hashmap_new(&string_hash_ops
);
957 for (i
= 0; i
< (unsigned)n
; i
++) {
958 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
962 unit_times_hashmap
= h
;
964 pager_open(arg_no_pager
, false);
966 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
967 "The time the unit takes to start is printed after the \"+\" character.\n");
969 if (!strv_isempty(names
)) {
971 STRV_FOREACH(name
, names
)
972 list_dependencies(bus
, *name
);
974 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
977 free_unit_times(times
, (unsigned) n
);
981 static int analyze_blame(sd_bus
*bus
) {
982 struct unit_times
*times
;
986 n
= acquire_time_data(bus
, ×
);
990 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
992 pager_open(arg_no_pager
, false);
994 for (i
= 0; i
< (unsigned) n
; i
++) {
995 char ts
[FORMAT_TIMESPAN_MAX
];
997 if (times
[i
].time
> 0)
998 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1001 free_unit_times(times
, (unsigned) n
);
1005 static int analyze_time(sd_bus
*bus
) {
1006 _cleanup_free_
char *buf
= NULL
;
1009 r
= pretty_boot_time(bus
, &buf
);
1017 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
[]) {
1018 _cleanup_strv_free_
char **units
= NULL
;
1021 bool match_patterns
;
1027 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1029 if (!strv_isempty(from_patterns
) &&
1031 !strv_fnmatch(from_patterns
, u
->id
, 0))
1034 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1038 STRV_FOREACH(unit
, units
) {
1039 bool match_patterns2
;
1041 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1043 if (!strv_isempty(to_patterns
) &&
1045 !strv_fnmatch(to_patterns
, *unit
, 0))
1048 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1051 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1057 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1063 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1064 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1069 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1070 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1073 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1076 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1079 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1087 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1088 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1092 STRV_FOREACH(pattern
, patterns
) {
1093 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1094 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1096 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1099 if (string_is_glob(*pattern
))
1102 unit
= unit_dbus_path_from_name(*pattern
);
1106 r
= sd_bus_get_property_string(
1108 "org.freedesktop.systemd1",
1110 "org.freedesktop.systemd1.Unit",
1115 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1117 if (!streq(*pattern
, unit_id
)) {
1118 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1123 *ret
= expanded_patterns
;
1124 expanded_patterns
= NULL
; /* do not free */
1129 static int dot(sd_bus
*bus
, char* patterns
[]) {
1130 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1131 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1132 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1133 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1134 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1138 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1142 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1146 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1150 r
= sd_bus_call_method(
1152 "org.freedesktop.systemd1",
1153 "/org/freedesktop/systemd1",
1154 "org.freedesktop.systemd1.Manager",
1160 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1164 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1166 return bus_log_parse_error(r
);
1168 printf("digraph systemd {\n");
1170 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1172 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1177 return bus_log_parse_error(r
);
1181 log_info(" Color legend: black = Requires\n"
1182 " dark blue = Requisite\n"
1183 " dark grey = Wants\n"
1184 " red = Conflicts\n"
1185 " green = After\n");
1188 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1189 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1194 static int dump(sd_bus
*bus
, char **args
) {
1195 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1196 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1197 const char *text
= NULL
;
1200 if (!strv_isempty(args
)) {
1201 log_error("Too many arguments.");
1205 pager_open(arg_no_pager
, false);
1207 r
= sd_bus_call_method(
1209 "org.freedesktop.systemd1",
1210 "/org/freedesktop/systemd1",
1211 "org.freedesktop.systemd1.Manager",
1217 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1219 r
= sd_bus_message_read(reply
, "s", &text
);
1221 return bus_log_parse_error(r
);
1223 fputs(text
, stdout
);
1227 static int set_log_level(sd_bus
*bus
, char **args
) {
1228 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1234 if (strv_length(args
) != 1) {
1235 log_error("This command expects one argument only.");
1239 r
= sd_bus_set_property(
1241 "org.freedesktop.systemd1",
1242 "/org/freedesktop/systemd1",
1243 "org.freedesktop.systemd1.Manager",
1249 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1254 static int set_log_target(sd_bus
*bus
, char **args
) {
1255 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1261 if (strv_length(args
) != 1) {
1262 log_error("This command expects one argument only.");
1266 r
= sd_bus_set_property(
1268 "org.freedesktop.systemd1",
1269 "/org/freedesktop/systemd1",
1270 "org.freedesktop.systemd1.Manager",
1276 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1282 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1283 const char *syscall
;
1285 printf("%s\n", set
->name
);
1286 printf(" # %s\n", set
->help
);
1287 NULSTR_FOREACH(syscall
, set
->value
)
1288 printf(" %s\n", syscall
);
1291 static int dump_syscall_filters(char** names
) {
1294 pager_open(arg_no_pager
, false);
1296 if (strv_isempty(names
)) {
1299 for (i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1302 dump_syscall_filter(syscall_filter_sets
+ i
);
1308 STRV_FOREACH(name
, names
) {
1309 const SyscallFilterSet
*set
;
1314 set
= syscall_filter_set_find(*name
);
1316 /* make sure the error appears below normal output */
1319 log_error("Filter set \"%s\" not found.", *name
);
1323 dump_syscall_filter(set
);
1332 static int dump_syscall_filters(char** names
) {
1333 log_error("Not compiled with syscall filters, sorry.");
1338 static void help(void) {
1340 pager_open(arg_no_pager
, false);
1342 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1343 "Profile systemd, show unit dependencies, check unit files.\n\n"
1344 " -h --help Show this help\n"
1345 " --version Show package version\n"
1346 " --no-pager Do not pipe output into a pager\n"
1347 " --system Operate on system systemd instance\n"
1348 " --user Operate on user systemd instance\n"
1349 " -H --host=[USER@]HOST Operate on remote host\n"
1350 " -M --machine=CONTAINER Operate on local container\n"
1351 " --order Show only order in the graph\n"
1352 " --require Show only requirement in the graph\n"
1353 " --from-pattern=GLOB Show only origins in the graph\n"
1354 " --to-pattern=GLOB Show only destinations in the graph\n"
1355 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1356 " earlier than the latest in the branch\n"
1357 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1359 " time Print time spent in the kernel\n"
1360 " blame Print list of running units ordered by time to init\n"
1361 " critical-chain Print a tree of the time critical chain of units\n"
1362 " plot Output SVG graphic showing service initialization\n"
1363 " dot Output dependency graph in dot(1) format\n"
1364 " set-log-level LEVEL Set logging threshold for manager\n"
1365 " set-log-target TARGET Set logging target for manager\n"
1366 " dump Output state serialization of service manager\n"
1367 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
1368 " verify FILE... Check unit files for correctness\n"
1369 , program_invocation_short_name
);
1371 /* When updating this list, including descriptions, apply
1372 * changes to shell-completion/bash/systemd-analyze and
1373 * shell-completion/zsh/_systemd-analyze too. */
1376 static int parse_argv(int argc
, char *argv
[]) {
1378 ARG_VERSION
= 0x100,
1383 ARG_DOT_FROM_PATTERN
,
1390 static const struct option options
[] = {
1391 { "help", no_argument
, NULL
, 'h' },
1392 { "version", no_argument
, NULL
, ARG_VERSION
},
1393 { "order", no_argument
, NULL
, ARG_ORDER
},
1394 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1395 { "user", no_argument
, NULL
, ARG_USER
},
1396 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1397 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1398 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1399 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1400 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1401 { "man", optional_argument
, NULL
, ARG_MAN
},
1402 { "host", required_argument
, NULL
, 'H' },
1403 { "machine", required_argument
, NULL
, 'M' },
1412 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1431 arg_dot
= DEP_ORDER
;
1435 arg_dot
= DEP_REQUIRE
;
1438 case ARG_DOT_FROM_PATTERN
:
1439 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1444 case ARG_DOT_TO_PATTERN
:
1445 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1451 r
= parse_sec(optarg
, &arg_fuzz
);
1457 arg_no_pager
= true;
1461 arg_transport
= BUS_TRANSPORT_REMOTE
;
1466 arg_transport
= BUS_TRANSPORT_MACHINE
;
1472 r
= parse_boolean(optarg
);
1474 log_error("Failed to parse --man= argument.");
1488 assert_not_reached("Unhandled option code.");
1491 return 1; /* work to do */
1494 int main(int argc
, char *argv
[]) {
1497 setlocale(LC_ALL
, "");
1498 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1499 log_parse_environment();
1502 r
= parse_argv(argc
, argv
);
1506 if (streq_ptr(argv
[optind
], "verify"))
1507 r
= verify_units(argv
+optind
+1,
1508 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1511 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1513 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1515 log_error_errno(r
, "Failed to create bus connection: %m");
1519 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1520 r
= analyze_time(bus
);
1521 else if (streq(argv
[optind
], "blame"))
1522 r
= analyze_blame(bus
);
1523 else if (streq(argv
[optind
], "critical-chain"))
1524 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1525 else if (streq(argv
[optind
], "plot"))
1526 r
= analyze_plot(bus
);
1527 else if (streq(argv
[optind
], "dot"))
1528 r
= dot(bus
, argv
+optind
+1);
1529 else if (streq(argv
[optind
], "dump"))
1530 r
= dump(bus
, argv
+optind
+1);
1531 else if (streq(argv
[optind
], "set-log-level"))
1532 r
= set_log_level(bus
, argv
+optind
+1);
1533 else if (streq(argv
[optind
], "set-log-target"))
1534 r
= set_log_target(bus
, argv
+optind
+1);
1535 else if (streq(argv
[optind
], "syscall-filter"))
1536 r
= dump_syscall_filters(argv
+optind
+1);
1538 log_error("Unknown operation '%s'.", argv
[optind
]);
1544 strv_free(arg_dot_from_patterns
);
1545 strv_free(arg_dot_to_patterns
);
1547 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;