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"
32 #include "glob-util.h"
34 #include "locale-util.h"
37 #include "parse-util.h"
41 #include "terminal-util.h"
42 #include "unit-name.h"
45 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
46 #define SCALE_Y (20.0)
48 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
50 #define svg(...) printf(__VA_ARGS__)
52 #define svg_bar(class, x1, x2, y) \
53 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
55 SCALE_X * (x1), SCALE_Y * (y), \
56 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
58 #define svg_text(b, x, y, format, ...) \
60 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
61 svg(format, ## __VA_ARGS__); \
70 static char** arg_dot_from_patterns
= NULL
;
71 static char** arg_dot_to_patterns
= NULL
;
72 static usec_t arg_fuzz
= 0;
73 static bool arg_no_pager
= false;
74 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
75 static char *arg_host
= NULL
;
76 static bool arg_user
= false;
77 static bool arg_man
= true;
83 usec_t kernel_done_time
;
85 usec_t userspace_time
;
87 usec_t security_start_time
;
88 usec_t security_finish_time
;
89 usec_t generators_start_time
;
90 usec_t generators_finish_time
;
91 usec_t unitsload_start_time
;
92 usec_t unitsload_finish_time
;
95 * If we're analyzing the user instance, all timestamps will be offset
96 * by its own start-up timestamp, which may be arbitrarily big.
97 * With "plot", this causes arbitrarily wide output SVG files which almost
98 * completely consist of empty space. Thus we cancel out this offset.
100 * This offset is subtracted from times above by acquire_boot_times(),
101 * but it still needs to be subtracted from unit-specific timestamps
102 * (so it is stored here for reference).
104 usec_t reverse_offset
;
119 char *kernel_release
;
120 char *kernel_version
;
121 char *os_pretty_name
;
122 char *virtualization
;
126 static void pager_open_if_enabled(void) {
134 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
135 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
144 r
= sd_bus_get_property_trivial(
146 "org.freedesktop.systemd1",
154 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
161 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
162 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
170 r
= sd_bus_get_property_strv(
172 "org.freedesktop.systemd1",
174 "org.freedesktop.systemd1.Unit",
179 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
186 static int compare_unit_time(const void *a
, const void *b
) {
187 return compare(((struct unit_times
*)b
)->time
,
188 ((struct unit_times
*)a
)->time
);
191 static int compare_unit_start(const void *a
, const void *b
) {
192 return compare(((struct unit_times
*)a
)->activating
,
193 ((struct unit_times
*)b
)->activating
);
196 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
197 struct unit_times
*p
;
199 for (p
= t
; p
< t
+ n
; p
++)
205 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
214 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
215 static struct boot_times times
;
216 static bool cached
= false;
221 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
223 if (bus_get_uint64_property(bus
,
224 "/org/freedesktop/systemd1",
225 "org.freedesktop.systemd1.Manager",
226 "FirmwareTimestampMonotonic",
227 ×
.firmware_time
) < 0 ||
228 bus_get_uint64_property(bus
,
229 "/org/freedesktop/systemd1",
230 "org.freedesktop.systemd1.Manager",
231 "LoaderTimestampMonotonic",
232 ×
.loader_time
) < 0 ||
233 bus_get_uint64_property(bus
,
234 "/org/freedesktop/systemd1",
235 "org.freedesktop.systemd1.Manager",
237 ×
.kernel_time
) < 0 ||
238 bus_get_uint64_property(bus
,
239 "/org/freedesktop/systemd1",
240 "org.freedesktop.systemd1.Manager",
241 "InitRDTimestampMonotonic",
242 ×
.initrd_time
) < 0 ||
243 bus_get_uint64_property(bus
,
244 "/org/freedesktop/systemd1",
245 "org.freedesktop.systemd1.Manager",
246 "UserspaceTimestampMonotonic",
247 ×
.userspace_time
) < 0 ||
248 bus_get_uint64_property(bus
,
249 "/org/freedesktop/systemd1",
250 "org.freedesktop.systemd1.Manager",
251 "FinishTimestampMonotonic",
252 ×
.finish_time
) < 0 ||
253 bus_get_uint64_property(bus
,
254 "/org/freedesktop/systemd1",
255 "org.freedesktop.systemd1.Manager",
256 "SecurityStartTimestampMonotonic",
257 ×
.security_start_time
) < 0 ||
258 bus_get_uint64_property(bus
,
259 "/org/freedesktop/systemd1",
260 "org.freedesktop.systemd1.Manager",
261 "SecurityFinishTimestampMonotonic",
262 ×
.security_finish_time
) < 0 ||
263 bus_get_uint64_property(bus
,
264 "/org/freedesktop/systemd1",
265 "org.freedesktop.systemd1.Manager",
266 "GeneratorsStartTimestampMonotonic",
267 ×
.generators_start_time
) < 0 ||
268 bus_get_uint64_property(bus
,
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "GeneratorsFinishTimestampMonotonic",
272 ×
.generators_finish_time
) < 0 ||
273 bus_get_uint64_property(bus
,
274 "/org/freedesktop/systemd1",
275 "org.freedesktop.systemd1.Manager",
276 "UnitsLoadStartTimestampMonotonic",
277 ×
.unitsload_start_time
) < 0 ||
278 bus_get_uint64_property(bus
,
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
281 "UnitsLoadFinishTimestampMonotonic",
282 ×
.unitsload_finish_time
) < 0)
285 if (times
.finish_time
<= 0) {
286 log_error("Bootup is not yet finished. Please try again later.");
292 * User-instance-specific timestamps processing
293 * (see comment to reverse_offset in struct boot_times).
295 times
.reverse_offset
= times
.userspace_time
;
297 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
298 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
301 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
303 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
304 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
306 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
307 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
309 if (times
.initrd_time
)
310 times
.kernel_done_time
= times
.initrd_time
;
312 times
.kernel_done_time
= times
.userspace_time
;
322 static void free_host_info(struct host_info
*hi
) {
328 free(hi
->kernel_name
);
329 free(hi
->kernel_release
);
330 free(hi
->kernel_version
);
331 free(hi
->os_pretty_name
);
332 free(hi
->virtualization
);
333 free(hi
->architecture
);
337 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
339 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
340 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
341 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
343 struct boot_times
*boot_times
= NULL
;
344 struct unit_times
*unit_times
= NULL
;
348 r
= acquire_boot_times(bus
, &boot_times
);
352 r
= sd_bus_call_method(
354 "org.freedesktop.systemd1",
355 "/org/freedesktop/systemd1",
356 "org.freedesktop.systemd1.Manager",
361 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
365 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
367 bus_log_parse_error(r
);
371 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
372 struct unit_times
*t
;
374 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
382 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
384 if (bus_get_uint64_property(bus
, u
.unit_path
,
385 "org.freedesktop.systemd1.Unit",
386 "InactiveExitTimestampMonotonic",
387 &t
->activating
) < 0 ||
388 bus_get_uint64_property(bus
, u
.unit_path
,
389 "org.freedesktop.systemd1.Unit",
390 "ActiveEnterTimestampMonotonic",
391 &t
->activated
) < 0 ||
392 bus_get_uint64_property(bus
, u
.unit_path
,
393 "org.freedesktop.systemd1.Unit",
394 "ActiveExitTimestampMonotonic",
395 &t
->deactivating
) < 0 ||
396 bus_get_uint64_property(bus
, u
.unit_path
,
397 "org.freedesktop.systemd1.Unit",
398 "InactiveEnterTimestampMonotonic",
399 &t
->deactivated
) < 0) {
404 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
405 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
406 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
407 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
409 if (t
->activated
>= t
->activating
)
410 t
->time
= t
->activated
- t
->activating
;
411 else if (t
->deactivated
>= t
->activating
)
412 t
->time
= t
->deactivated
- t
->activating
;
416 if (t
->activating
== 0)
419 t
->name
= strdup(u
.id
);
420 if (t
->name
== NULL
) {
427 bus_log_parse_error(r
);
436 free_unit_times(unit_times
, (unsigned) c
);
440 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
441 static const struct bus_properties_map hostname_map
[] = {
442 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
443 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
444 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
445 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
446 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
450 static const struct bus_properties_map manager_map
[] = {
451 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
452 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
456 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
457 _cleanup_(free_host_infop
) struct host_info
*host
;
460 host
= new0(struct host_info
, 1);
464 r
= bus_map_all_properties(bus
,
465 "org.freedesktop.hostname1",
466 "/org/freedesktop/hostname1",
470 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
472 r
= bus_map_all_properties(bus
,
473 "org.freedesktop.systemd1",
474 "/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", 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", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
764 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_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_if_enabled();
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_if_enabled();
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_if_enabled();
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 set_log_target(sd_bus
*bus
, char **args
) {
1259 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1265 if (strv_length(args
) != 1) {
1266 log_error("This command expects one argument only.");
1270 r
= sd_bus_set_property(
1272 "org.freedesktop.systemd1",
1273 "/org/freedesktop/systemd1",
1274 "org.freedesktop.systemd1.Manager",
1280 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1285 static void help(void) {
1287 pager_open_if_enabled();
1289 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1290 "Profile systemd, show unit dependencies, check unit files.\n\n"
1291 " -h --help Show this help\n"
1292 " --version Show package version\n"
1293 " --no-pager Do not pipe output into a pager\n"
1294 " --system Operate on system systemd instance\n"
1295 " --user Operate on user systemd instance\n"
1296 " -H --host=[USER@]HOST Operate on remote host\n"
1297 " -M --machine=CONTAINER Operate on local container\n"
1298 " --order Show only order in the graph\n"
1299 " --require Show only requirement in the graph\n"
1300 " --from-pattern=GLOB Show only origins in the graph\n"
1301 " --to-pattern=GLOB Show only destinations in the graph\n"
1302 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1303 " earlier than the latest in the branch\n"
1304 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1306 " time Print time spent in the kernel\n"
1307 " blame Print list of running units ordered by time to init\n"
1308 " critical-chain Print a tree of the time critical chain of units\n"
1309 " plot Output SVG graphic showing service initialization\n"
1310 " dot Output dependency graph in dot(1) format\n"
1311 " set-log-level LEVEL Set logging threshold for manager\n"
1312 " set-log-target TARGET Set logging target for manager\n"
1313 " dump Output state serialization of service manager\n"
1314 " verify FILE... Check unit files for correctness\n"
1315 , program_invocation_short_name
);
1317 /* When updating this list, including descriptions, apply
1318 * changes to shell-completion/bash/systemd-analyze and
1319 * shell-completion/zsh/_systemd-analyze too. */
1322 static int parse_argv(int argc
, char *argv
[]) {
1324 ARG_VERSION
= 0x100,
1329 ARG_DOT_FROM_PATTERN
,
1336 static const struct option options
[] = {
1337 { "help", no_argument
, NULL
, 'h' },
1338 { "version", no_argument
, NULL
, ARG_VERSION
},
1339 { "order", no_argument
, NULL
, ARG_ORDER
},
1340 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1341 { "user", no_argument
, NULL
, ARG_USER
},
1342 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1343 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1344 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1345 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1346 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1347 { "man", optional_argument
, NULL
, ARG_MAN
},
1348 { "host", required_argument
, NULL
, 'H' },
1349 { "machine", required_argument
, NULL
, 'M' },
1358 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1377 arg_dot
= DEP_ORDER
;
1381 arg_dot
= DEP_REQUIRE
;
1384 case ARG_DOT_FROM_PATTERN
:
1385 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1390 case ARG_DOT_TO_PATTERN
:
1391 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1397 r
= parse_sec(optarg
, &arg_fuzz
);
1403 arg_no_pager
= true;
1407 arg_transport
= BUS_TRANSPORT_REMOTE
;
1412 arg_transport
= BUS_TRANSPORT_MACHINE
;
1418 r
= parse_boolean(optarg
);
1420 log_error("Failed to parse --man= argument.");
1434 assert_not_reached("Unhandled option code.");
1437 return 1; /* work to do */
1440 int main(int argc
, char *argv
[]) {
1443 setlocale(LC_ALL
, "");
1444 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1445 log_parse_environment();
1448 r
= parse_argv(argc
, argv
);
1452 if (streq_ptr(argv
[optind
], "verify"))
1453 r
= verify_units(argv
+optind
+1,
1454 arg_user
? MANAGER_USER
: MANAGER_SYSTEM
,
1457 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1459 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1461 log_error_errno(r
, "Failed to create bus connection: %m");
1465 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1466 r
= analyze_time(bus
);
1467 else if (streq(argv
[optind
], "blame"))
1468 r
= analyze_blame(bus
);
1469 else if (streq(argv
[optind
], "critical-chain"))
1470 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1471 else if (streq(argv
[optind
], "plot"))
1472 r
= analyze_plot(bus
);
1473 else if (streq(argv
[optind
], "dot"))
1474 r
= dot(bus
, argv
+optind
+1);
1475 else if (streq(argv
[optind
], "dump"))
1476 r
= dump(bus
, argv
+optind
+1);
1477 else if (streq(argv
[optind
], "set-log-level"))
1478 r
= set_log_level(bus
, argv
+optind
+1);
1479 else if (streq(argv
[optind
], "set-log-target"))
1480 r
= set_log_target(bus
, argv
+optind
+1);
1482 log_error("Unknown operation '%s'.", argv
[optind
]);
1488 strv_free(arg_dot_from_patterns
);
1489 strv_free(arg_dot_to_patterns
);
1491 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;