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",
467 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
469 r
= bus_map_all_properties(bus
,
470 "org.freedesktop.systemd1",
471 "/org/freedesktop/systemd1",
476 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
484 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
485 char ts
[FORMAT_TIMESPAN_MAX
];
486 struct boot_times
*t
;
487 static char buf
[4096];
492 r
= acquire_boot_times(bus
, &t
);
499 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
500 if (t
->firmware_time
)
501 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
503 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
505 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
506 if (t
->initrd_time
> 0)
507 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
509 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
510 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
520 static void svg_graph_box(double height
, double begin
, double end
) {
523 /* outside box, fill */
524 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
525 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
527 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
528 /* lines for each second */
529 if (i
% 5000000 == 0)
530 svg(" <line class=\"sec5\" 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
);
533 else if (i
% 1000000 == 0)
534 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
535 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
536 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
538 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
539 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
543 static int analyze_plot(sd_bus
*bus
) {
544 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
545 struct unit_times
*times
;
546 struct boot_times
*boot
;
549 _cleanup_free_
char *pretty_times
= NULL
;
550 struct unit_times
*u
;
552 n
= acquire_boot_times(bus
, &boot
);
556 n
= pretty_boot_time(bus
, &pretty_times
);
560 n
= acquire_host_info(bus
, &host
);
564 n
= acquire_time_data(bus
, ×
);
568 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
570 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
574 if (boot
->firmware_time
> boot
->loader_time
)
576 if (boot
->loader_time
) {
581 if (boot
->initrd_time
)
583 if (boot
->kernel_time
)
586 for (u
= times
; u
< times
+ n
; u
++) {
587 double text_start
, text_width
;
589 if (u
->activating
< boot
->userspace_time
||
590 u
->activating
> boot
->finish_time
) {
591 u
->name
= mfree(u
->name
);
595 /* If the text cannot fit on the left side then
596 * increase the svg width so it fits on the right.
597 * TODO: calculate the text width more accurately */
598 text_width
= 8.0 * strlen(u
->name
);
599 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
600 if (text_width
> text_start
&& text_width
+ text_start
> width
)
601 width
= text_width
+ text_start
;
603 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
604 && u
->activated
== 0 && u
->deactivating
== 0)
605 u
->activated
= u
->deactivating
= u
->deactivated
;
606 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
607 u
->activated
= boot
->finish_time
;
608 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
609 u
->deactivating
= boot
->finish_time
;
610 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
611 u
->deactivated
= boot
->finish_time
;
615 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
616 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
617 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
619 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
620 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
621 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
622 5 * SCALE_Y
/* legend */);
624 /* write some basic info as a comment, including some help */
625 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
626 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
627 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
628 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
629 "<!-- point your browser to this file. -->\n\n"
630 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
633 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
634 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
635 " rect.background { fill: rgb(255,255,255); }\n"
636 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
637 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
638 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
639 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
640 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
641 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
643 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
644 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
645 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
646 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
647 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
648 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
650 " line.sec5 { stroke-width: 2; }\n"
651 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
652 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
653 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
654 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
655 " text.sec { font-size: 10px; }\n"
656 " ]]>\n </style>\n</defs>\n\n");
658 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
659 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
660 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
661 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
662 strempty(host
->hostname
),
663 strempty(host
->kernel_name
),
664 strempty(host
->kernel_release
),
665 strempty(host
->kernel_version
),
666 strempty(host
->architecture
),
667 strempty(host
->virtualization
));
669 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
670 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
672 if (boot
->firmware_time
) {
673 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
674 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
677 if (boot
->loader_time
) {
678 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
679 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
682 if (boot
->kernel_time
) {
683 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
684 svg_text(true, 0, y
, "kernel");
687 if (boot
->initrd_time
) {
688 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
689 svg_text(true, boot
->initrd_time
, y
, "initrd");
692 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
693 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
694 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
695 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
696 svg_text(true, boot
->userspace_time
, y
, "systemd");
699 for (u
= times
; u
< times
+ n
; u
++) {
700 char ts
[FORMAT_TIMESPAN_MAX
];
706 svg_bar("activating", u
->activating
, u
->activated
, y
);
707 svg_bar("active", u
->activated
, u
->deactivating
, y
);
708 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
710 /* place the text on the left if we have passed the half of the svg width */
711 b
= u
->activating
* SCALE_X
< width
/ 2;
713 svg_text(b
, u
->activating
, y
, "%s (%s)",
714 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
716 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
723 svg("<g transform=\"translate(20,100)\">\n");
725 svg_bar("activating", 0, 300000, y
);
726 svg_text(true, 400000, y
, "Activating");
728 svg_bar("active", 0, 300000, y
);
729 svg_text(true, 400000, y
, "Active");
731 svg_bar("deactivating", 0, 300000, y
);
732 svg_text(true, 400000, y
, "Deactivating");
734 svg_bar("security", 0, 300000, y
);
735 svg_text(true, 400000, y
, "Setting up security module");
737 svg_bar("generators", 0, 300000, y
);
738 svg_text(true, 400000, y
, "Generators");
740 svg_bar("unitsload", 0, 300000, y
);
741 svg_text(true, 400000, y
, "Loading unit files");
748 free_unit_times(times
, (unsigned) n
);
754 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
755 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
757 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
759 for (i
= level
; i
!= 0; i
--)
760 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
762 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
766 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
767 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
768 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ansi_normal());
769 else if (times
->activated
> boot
->userspace_time
)
770 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
780 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
781 _cleanup_free_
char *path
= NULL
;
787 path
= unit_dbus_path_from_name(name
);
791 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
794 static Hashmap
*unit_times_hashmap
;
796 static int list_dependencies_compare(const void *_a
, const void *_b
) {
797 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
798 usec_t usa
= 0, usb
= 0;
799 struct unit_times
*times
;
801 times
= hashmap_get(unit_times_hashmap
, *a
);
803 usa
= times
->activated
;
804 times
= hashmap_get(unit_times_hashmap
, *b
);
806 usb
= times
->activated
;
811 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
812 unsigned int branches
) {
813 _cleanup_strv_free_
char **deps
= NULL
;
816 usec_t service_longest
= 0;
818 struct unit_times
*times
;
819 struct boot_times
*boot
;
821 if (strv_extend(units
, name
))
824 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
828 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
830 r
= acquire_boot_times(bus
, &boot
);
834 STRV_FOREACH(c
, deps
) {
835 times
= hashmap_get(unit_times_hashmap
, *c
);
838 && times
->activated
<= boot
->finish_time
839 && (times
->activated
>= service_longest
840 || service_longest
== 0)) {
841 service_longest
= times
->activated
;
846 if (service_longest
== 0 )
849 STRV_FOREACH(c
, deps
) {
850 times
= hashmap_get(unit_times_hashmap
, *c
);
851 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
858 STRV_FOREACH(c
, deps
) {
859 times
= hashmap_get(unit_times_hashmap
, *c
);
862 || times
->activated
> boot
->finish_time
863 || service_longest
- times
->activated
> arg_fuzz
)
868 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
872 if (strv_contains(*units
, *c
)) {
873 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
880 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
881 (branches
<< 1) | (to_print
? 1 : 0));
891 static int list_dependencies(sd_bus
*bus
, const char *name
) {
892 _cleanup_strv_free_
char **units
= NULL
;
893 char ts
[FORMAT_TIMESPAN_MAX
];
894 struct unit_times
*times
;
897 _cleanup_free_
char *path
= NULL
;
898 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
899 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
900 struct boot_times
*boot
;
904 path
= unit_dbus_path_from_name(name
);
908 r
= sd_bus_get_property(
910 "org.freedesktop.systemd1",
912 "org.freedesktop.systemd1.Unit",
918 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
922 r
= sd_bus_message_read(reply
, "s", &id
);
924 return bus_log_parse_error(r
);
926 times
= hashmap_get(unit_times_hashmap
, id
);
928 r
= acquire_boot_times(bus
, &boot
);
934 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
935 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ansi_normal());
936 else if (times
->activated
> boot
->userspace_time
)
937 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
942 return list_dependencies_one(bus
, name
, 0, &units
, 0);
945 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
946 struct unit_times
*times
;
951 n
= acquire_time_data(bus
, ×
);
955 h
= hashmap_new(&string_hash_ops
);
959 for (i
= 0; i
< (unsigned)n
; i
++) {
960 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
964 unit_times_hashmap
= h
;
966 pager_open(arg_no_pager
, false);
968 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
969 "The time the unit takes to start is printed after the \"+\" character.\n");
971 if (!strv_isempty(names
)) {
973 STRV_FOREACH(name
, names
)
974 list_dependencies(bus
, *name
);
976 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
979 free_unit_times(times
, (unsigned) n
);
983 static int analyze_blame(sd_bus
*bus
) {
984 struct unit_times
*times
;
988 n
= acquire_time_data(bus
, ×
);
992 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
994 pager_open(arg_no_pager
, false);
996 for (i
= 0; i
< (unsigned) n
; i
++) {
997 char ts
[FORMAT_TIMESPAN_MAX
];
999 if (times
[i
].time
> 0)
1000 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1003 free_unit_times(times
, (unsigned) n
);
1007 static int analyze_time(sd_bus
*bus
) {
1008 _cleanup_free_
char *buf
= NULL
;
1011 r
= pretty_boot_time(bus
, &buf
);
1019 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
[]) {
1020 _cleanup_strv_free_
char **units
= NULL
;
1023 bool match_patterns
;
1029 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1031 if (!strv_isempty(from_patterns
) &&
1033 !strv_fnmatch(from_patterns
, u
->id
, 0))
1036 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1040 STRV_FOREACH(unit
, units
) {
1041 bool match_patterns2
;
1043 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1045 if (!strv_isempty(to_patterns
) &&
1047 !strv_fnmatch(to_patterns
, *unit
, 0))
1050 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1053 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1059 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1065 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1066 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1071 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1072 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1075 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1078 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1081 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1089 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1090 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1094 STRV_FOREACH(pattern
, patterns
) {
1095 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1096 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1098 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1101 if (string_is_glob(*pattern
))
1104 unit
= unit_dbus_path_from_name(*pattern
);
1108 r
= sd_bus_get_property_string(
1110 "org.freedesktop.systemd1",
1112 "org.freedesktop.systemd1.Unit",
1117 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1119 if (!streq(*pattern
, unit_id
)) {
1120 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1125 *ret
= expanded_patterns
;
1126 expanded_patterns
= NULL
; /* do not free */
1131 static int dot(sd_bus
*bus
, char* patterns
[]) {
1132 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1133 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1134 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1135 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1136 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1140 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1144 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1148 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1152 r
= sd_bus_call_method(
1154 "org.freedesktop.systemd1",
1155 "/org/freedesktop/systemd1",
1156 "org.freedesktop.systemd1.Manager",
1162 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1166 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1168 return bus_log_parse_error(r
);
1170 printf("digraph systemd {\n");
1172 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1174 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1179 return bus_log_parse_error(r
);
1183 log_info(" Color legend: black = Requires\n"
1184 " dark blue = Requisite\n"
1185 " dark grey = Wants\n"
1186 " red = Conflicts\n"
1187 " green = After\n");
1190 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1191 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1196 static int dump(sd_bus
*bus
, char **args
) {
1197 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1198 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1199 const char *text
= NULL
;
1202 if (!strv_isempty(args
)) {
1203 log_error("Too many arguments.");
1207 pager_open(arg_no_pager
, false);
1209 r
= sd_bus_call_method(
1211 "org.freedesktop.systemd1",
1212 "/org/freedesktop/systemd1",
1213 "org.freedesktop.systemd1.Manager",
1219 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1221 r
= sd_bus_message_read(reply
, "s", &text
);
1223 return bus_log_parse_error(r
);
1225 fputs(text
, stdout
);
1229 static int set_log_level(sd_bus
*bus
, char **args
) {
1230 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1236 if (strv_length(args
) != 1) {
1237 log_error("This command expects one argument only.");
1241 r
= sd_bus_set_property(
1243 "org.freedesktop.systemd1",
1244 "/org/freedesktop/systemd1",
1245 "org.freedesktop.systemd1.Manager",
1251 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1256 static int set_log_target(sd_bus
*bus
, char **args
) {
1257 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1263 if (strv_length(args
) != 1) {
1264 log_error("This command expects one argument only.");
1268 r
= sd_bus_set_property(
1270 "org.freedesktop.systemd1",
1271 "/org/freedesktop/systemd1",
1272 "org.freedesktop.systemd1.Manager",
1278 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1284 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1285 const char *syscall
;
1287 printf("%s\n", set
->name
);
1288 printf(" # %s\n", set
->help
);
1289 NULSTR_FOREACH(syscall
, set
->value
)
1290 printf(" %s\n", syscall
);
1293 static int dump_syscall_filters(char** names
) {
1296 pager_open(arg_no_pager
, false);
1298 if (strv_isempty(names
)) {
1301 for (i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1304 dump_syscall_filter(syscall_filter_sets
+ i
);
1310 STRV_FOREACH(name
, names
) {
1311 const SyscallFilterSet
*set
;
1316 set
= syscall_filter_set_find(*name
);
1318 /* make sure the error appears below normal output */
1321 log_error("Filter set \"%s\" not found.", *name
);
1325 dump_syscall_filter(set
);
1334 static int dump_syscall_filters(char** names
) {
1335 log_error("Not compiled with syscall filters, sorry.");
1340 static void help(void) {
1342 pager_open(arg_no_pager
, false);
1344 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1345 "Profile systemd, show unit dependencies, check unit files.\n\n"
1346 " -h --help Show this help\n"
1347 " --version Show package version\n"
1348 " --no-pager Do not pipe output into a pager\n"
1349 " --system Operate on system systemd instance\n"
1350 " --user Operate on user systemd instance\n"
1351 " -H --host=[USER@]HOST Operate on remote host\n"
1352 " -M --machine=CONTAINER Operate on local container\n"
1353 " --order Show only order in the graph\n"
1354 " --require Show only requirement in the graph\n"
1355 " --from-pattern=GLOB Show only origins in the graph\n"
1356 " --to-pattern=GLOB Show only destinations in the graph\n"
1357 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1358 " earlier than the latest in the branch\n"
1359 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1361 " time Print time spent in the kernel\n"
1362 " blame Print list of running units ordered by time to init\n"
1363 " critical-chain Print a tree of the time critical chain of units\n"
1364 " plot Output SVG graphic showing service initialization\n"
1365 " dot Output dependency graph in dot(1) format\n"
1366 " set-log-level LEVEL Set logging threshold for manager\n"
1367 " set-log-target TARGET Set logging target for manager\n"
1368 " dump Output state serialization of service manager\n"
1369 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
1370 " verify FILE... Check unit files for correctness\n"
1371 , program_invocation_short_name
);
1373 /* When updating this list, including descriptions, apply
1374 * changes to shell-completion/bash/systemd-analyze and
1375 * shell-completion/zsh/_systemd-analyze too. */
1378 static int parse_argv(int argc
, char *argv
[]) {
1380 ARG_VERSION
= 0x100,
1385 ARG_DOT_FROM_PATTERN
,
1392 static const struct option options
[] = {
1393 { "help", no_argument
, NULL
, 'h' },
1394 { "version", no_argument
, NULL
, ARG_VERSION
},
1395 { "order", no_argument
, NULL
, ARG_ORDER
},
1396 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1397 { "user", no_argument
, NULL
, ARG_USER
},
1398 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1399 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1400 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1401 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1402 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1403 { "man", optional_argument
, NULL
, ARG_MAN
},
1404 { "host", required_argument
, NULL
, 'H' },
1405 { "machine", required_argument
, NULL
, 'M' },
1414 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1433 arg_dot
= DEP_ORDER
;
1437 arg_dot
= DEP_REQUIRE
;
1440 case ARG_DOT_FROM_PATTERN
:
1441 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1446 case ARG_DOT_TO_PATTERN
:
1447 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1453 r
= parse_sec(optarg
, &arg_fuzz
);
1459 arg_no_pager
= true;
1463 arg_transport
= BUS_TRANSPORT_REMOTE
;
1468 arg_transport
= BUS_TRANSPORT_MACHINE
;
1474 r
= parse_boolean(optarg
);
1476 log_error("Failed to parse --man= argument.");
1490 assert_not_reached("Unhandled option code.");
1493 return 1; /* work to do */
1496 int main(int argc
, char *argv
[]) {
1499 setlocale(LC_ALL
, "");
1500 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1501 log_parse_environment();
1504 r
= parse_argv(argc
, argv
);
1508 if (streq_ptr(argv
[optind
], "verify"))
1509 r
= verify_units(argv
+optind
+1,
1510 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1513 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1515 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1517 log_error_errno(r
, "Failed to create bus connection: %m");
1521 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1522 r
= analyze_time(bus
);
1523 else if (streq(argv
[optind
], "blame"))
1524 r
= analyze_blame(bus
);
1525 else if (streq(argv
[optind
], "critical-chain"))
1526 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1527 else if (streq(argv
[optind
], "plot"))
1528 r
= analyze_plot(bus
);
1529 else if (streq(argv
[optind
], "dot"))
1530 r
= dot(bus
, argv
+optind
+1);
1531 else if (streq(argv
[optind
], "dump"))
1532 r
= dump(bus
, argv
+optind
+1);
1533 else if (streq(argv
[optind
], "set-log-level"))
1534 r
= set_log_level(bus
, argv
+optind
+1);
1535 else if (streq(argv
[optind
], "set-log-target"))
1536 r
= set_log_target(bus
, argv
+optind
+1);
1537 else if (streq(argv
[optind
], "syscall-filter"))
1538 r
= dump_syscall_filters(argv
+optind
+1);
1540 log_error("Unknown operation '%s'.", argv
[optind
]);
1546 strv_free(arg_dot_from_patterns
);
1547 strv_free(arg_dot_to_patterns
);
1549 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;