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 int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
127 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
136 r
= sd_bus_get_property_trivial(
138 "org.freedesktop.systemd1",
146 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
153 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
154 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
162 r
= sd_bus_get_property_strv(
164 "org.freedesktop.systemd1",
166 "org.freedesktop.systemd1.Unit",
171 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
178 static int compare_unit_time(const void *a
, const void *b
) {
179 return compare(((struct unit_times
*)b
)->time
,
180 ((struct unit_times
*)a
)->time
);
183 static int compare_unit_start(const void *a
, const void *b
) {
184 return compare(((struct unit_times
*)a
)->activating
,
185 ((struct unit_times
*)b
)->activating
);
188 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
189 struct unit_times
*p
;
191 for (p
= t
; p
< t
+ n
; p
++)
197 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
206 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
207 static struct boot_times times
;
208 static bool cached
= false;
213 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
215 if (bus_get_uint64_property(bus
,
216 "/org/freedesktop/systemd1",
217 "org.freedesktop.systemd1.Manager",
218 "FirmwareTimestampMonotonic",
219 ×
.firmware_time
) < 0 ||
220 bus_get_uint64_property(bus
,
221 "/org/freedesktop/systemd1",
222 "org.freedesktop.systemd1.Manager",
223 "LoaderTimestampMonotonic",
224 ×
.loader_time
) < 0 ||
225 bus_get_uint64_property(bus
,
226 "/org/freedesktop/systemd1",
227 "org.freedesktop.systemd1.Manager",
229 ×
.kernel_time
) < 0 ||
230 bus_get_uint64_property(bus
,
231 "/org/freedesktop/systemd1",
232 "org.freedesktop.systemd1.Manager",
233 "InitRDTimestampMonotonic",
234 ×
.initrd_time
) < 0 ||
235 bus_get_uint64_property(bus
,
236 "/org/freedesktop/systemd1",
237 "org.freedesktop.systemd1.Manager",
238 "UserspaceTimestampMonotonic",
239 ×
.userspace_time
) < 0 ||
240 bus_get_uint64_property(bus
,
241 "/org/freedesktop/systemd1",
242 "org.freedesktop.systemd1.Manager",
243 "FinishTimestampMonotonic",
244 ×
.finish_time
) < 0 ||
245 bus_get_uint64_property(bus
,
246 "/org/freedesktop/systemd1",
247 "org.freedesktop.systemd1.Manager",
248 "SecurityStartTimestampMonotonic",
249 ×
.security_start_time
) < 0 ||
250 bus_get_uint64_property(bus
,
251 "/org/freedesktop/systemd1",
252 "org.freedesktop.systemd1.Manager",
253 "SecurityFinishTimestampMonotonic",
254 ×
.security_finish_time
) < 0 ||
255 bus_get_uint64_property(bus
,
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
258 "GeneratorsStartTimestampMonotonic",
259 ×
.generators_start_time
) < 0 ||
260 bus_get_uint64_property(bus
,
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
263 "GeneratorsFinishTimestampMonotonic",
264 ×
.generators_finish_time
) < 0 ||
265 bus_get_uint64_property(bus
,
266 "/org/freedesktop/systemd1",
267 "org.freedesktop.systemd1.Manager",
268 "UnitsLoadStartTimestampMonotonic",
269 ×
.unitsload_start_time
) < 0 ||
270 bus_get_uint64_property(bus
,
271 "/org/freedesktop/systemd1",
272 "org.freedesktop.systemd1.Manager",
273 "UnitsLoadFinishTimestampMonotonic",
274 ×
.unitsload_finish_time
) < 0)
277 if (times
.finish_time
<= 0) {
278 log_error("Bootup is not yet finished. Please try again later.");
284 * User-instance-specific timestamps processing
285 * (see comment to reverse_offset in struct boot_times).
287 times
.reverse_offset
= times
.userspace_time
;
289 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
290 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
292 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
293 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
295 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
296 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
298 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
301 if (times
.initrd_time
)
302 times
.kernel_done_time
= times
.initrd_time
;
304 times
.kernel_done_time
= times
.userspace_time
;
314 static void free_host_info(struct host_info
*hi
) {
320 free(hi
->kernel_name
);
321 free(hi
->kernel_release
);
322 free(hi
->kernel_version
);
323 free(hi
->os_pretty_name
);
324 free(hi
->virtualization
);
325 free(hi
->architecture
);
329 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
331 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
332 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
333 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
335 struct boot_times
*boot_times
= NULL
;
336 struct unit_times
*unit_times
= NULL
;
340 r
= acquire_boot_times(bus
, &boot_times
);
344 r
= sd_bus_call_method(
346 "org.freedesktop.systemd1",
347 "/org/freedesktop/systemd1",
348 "org.freedesktop.systemd1.Manager",
353 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
357 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
359 bus_log_parse_error(r
);
363 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
364 struct unit_times
*t
;
366 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
374 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
376 if (bus_get_uint64_property(bus
, u
.unit_path
,
377 "org.freedesktop.systemd1.Unit",
378 "InactiveExitTimestampMonotonic",
379 &t
->activating
) < 0 ||
380 bus_get_uint64_property(bus
, u
.unit_path
,
381 "org.freedesktop.systemd1.Unit",
382 "ActiveEnterTimestampMonotonic",
383 &t
->activated
) < 0 ||
384 bus_get_uint64_property(bus
, u
.unit_path
,
385 "org.freedesktop.systemd1.Unit",
386 "ActiveExitTimestampMonotonic",
387 &t
->deactivating
) < 0 ||
388 bus_get_uint64_property(bus
, u
.unit_path
,
389 "org.freedesktop.systemd1.Unit",
390 "InactiveEnterTimestampMonotonic",
391 &t
->deactivated
) < 0) {
396 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
397 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
398 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
399 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
401 if (t
->activated
>= t
->activating
)
402 t
->time
= t
->activated
- t
->activating
;
403 else if (t
->deactivated
>= t
->activating
)
404 t
->time
= t
->deactivated
- t
->activating
;
408 if (t
->activating
== 0)
411 t
->name
= strdup(u
.id
);
412 if (t
->name
== NULL
) {
419 bus_log_parse_error(r
);
428 free_unit_times(unit_times
, (unsigned) c
);
432 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
433 static const struct bus_properties_map hostname_map
[] = {
434 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
435 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
436 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
437 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
438 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
442 static const struct bus_properties_map manager_map
[] = {
443 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
444 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
448 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
449 _cleanup_(free_host_infop
) struct host_info
*host
;
452 host
= new0(struct host_info
, 1);
456 r
= bus_map_all_properties(bus
,
457 "org.freedesktop.hostname1",
458 "/org/freedesktop/hostname1",
462 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
464 r
= bus_map_all_properties(bus
,
465 "org.freedesktop.systemd1",
466 "/org/freedesktop/systemd1",
470 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
478 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
479 char ts
[FORMAT_TIMESPAN_MAX
];
480 struct boot_times
*t
;
481 static char buf
[4096];
486 r
= acquire_boot_times(bus
, &t
);
493 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
494 if (t
->firmware_time
)
495 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
497 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
499 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
500 if (t
->initrd_time
> 0)
501 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
503 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
504 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
514 static void svg_graph_box(double height
, double begin
, double end
) {
517 /* outside box, fill */
518 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
519 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
521 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
522 /* lines for each second */
523 if (i
% 5000000 == 0)
524 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
525 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
526 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
527 else if (i
% 1000000 == 0)
528 svg(" <line class=\"sec1\" 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
);
532 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
533 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
537 static int analyze_plot(sd_bus
*bus
) {
538 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
539 struct unit_times
*times
;
540 struct boot_times
*boot
;
543 _cleanup_free_
char *pretty_times
= NULL
;
544 struct unit_times
*u
;
546 n
= acquire_boot_times(bus
, &boot
);
550 n
= pretty_boot_time(bus
, &pretty_times
);
554 n
= acquire_host_info(bus
, &host
);
558 n
= acquire_time_data(bus
, ×
);
562 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
564 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
568 if (boot
->firmware_time
> boot
->loader_time
)
570 if (boot
->loader_time
) {
575 if (boot
->initrd_time
)
577 if (boot
->kernel_time
)
580 for (u
= times
; u
< times
+ n
; u
++) {
581 double text_start
, text_width
;
583 if (u
->activating
< boot
->userspace_time
||
584 u
->activating
> boot
->finish_time
) {
585 u
->name
= mfree(u
->name
);
589 /* If the text cannot fit on the left side then
590 * increase the svg width so it fits on the right.
591 * TODO: calculate the text width more accurately */
592 text_width
= 8.0 * strlen(u
->name
);
593 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
594 if (text_width
> text_start
&& text_width
+ text_start
> width
)
595 width
= text_width
+ text_start
;
597 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
598 && u
->activated
== 0 && u
->deactivating
== 0)
599 u
->activated
= u
->deactivating
= u
->deactivated
;
600 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
601 u
->activated
= boot
->finish_time
;
602 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
603 u
->deactivating
= boot
->finish_time
;
604 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
605 u
->deactivated
= boot
->finish_time
;
609 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
610 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
611 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
613 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
614 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
615 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
616 5 * SCALE_Y
/* legend */);
618 /* write some basic info as a comment, including some help */
619 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
620 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
621 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
622 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
623 "<!-- point your browser to this file. -->\n\n"
624 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
627 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
628 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
629 " rect.background { fill: rgb(255,255,255); }\n"
630 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
631 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
632 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
633 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
634 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
635 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
636 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
637 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
638 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
639 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
640 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
641 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
642 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
644 " line.sec5 { stroke-width: 2; }\n"
645 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
646 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
647 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
648 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
649 " text.sec { font-size: 10px; }\n"
650 " ]]>\n </style>\n</defs>\n\n");
652 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
653 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
654 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
655 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
656 strempty(host
->hostname
),
657 strempty(host
->kernel_name
),
658 strempty(host
->kernel_release
),
659 strempty(host
->kernel_version
),
660 strempty(host
->architecture
),
661 strempty(host
->virtualization
));
663 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
664 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
666 if (boot
->firmware_time
) {
667 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
668 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
671 if (boot
->loader_time
) {
672 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
673 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
676 if (boot
->kernel_time
) {
677 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
678 svg_text(true, 0, y
, "kernel");
681 if (boot
->initrd_time
) {
682 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
683 svg_text(true, boot
->initrd_time
, y
, "initrd");
686 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
687 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
688 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
689 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
690 svg_text(true, boot
->userspace_time
, y
, "systemd");
693 for (u
= times
; u
< times
+ n
; u
++) {
694 char ts
[FORMAT_TIMESPAN_MAX
];
700 svg_bar("activating", u
->activating
, u
->activated
, y
);
701 svg_bar("active", u
->activated
, u
->deactivating
, y
);
702 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
704 /* place the text on the left if we have passed the half of the svg width */
705 b
= u
->activating
* SCALE_X
< width
/ 2;
707 svg_text(b
, u
->activating
, y
, "%s (%s)",
708 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
710 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
717 svg("<g transform=\"translate(20,100)\">\n");
719 svg_bar("activating", 0, 300000, y
);
720 svg_text(true, 400000, y
, "Activating");
722 svg_bar("active", 0, 300000, y
);
723 svg_text(true, 400000, y
, "Active");
725 svg_bar("deactivating", 0, 300000, y
);
726 svg_text(true, 400000, y
, "Deactivating");
728 svg_bar("security", 0, 300000, y
);
729 svg_text(true, 400000, y
, "Setting up security module");
731 svg_bar("generators", 0, 300000, y
);
732 svg_text(true, 400000, y
, "Generators");
734 svg_bar("unitsload", 0, 300000, y
);
735 svg_text(true, 400000, y
, "Loading unit files");
742 free_unit_times(times
, (unsigned) n
);
748 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
749 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
751 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
753 for (i
= level
; i
!= 0; i
--)
754 printf("%s", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
756 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_TREE_BRANCH
));
760 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED
, name
,
761 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
762 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
763 else if (times
->activated
> boot
->userspace_time
)
764 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
774 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
775 _cleanup_free_
char *path
= NULL
;
781 path
= unit_dbus_path_from_name(name
);
785 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
788 static Hashmap
*unit_times_hashmap
;
790 static int list_dependencies_compare(const void *_a
, const void *_b
) {
791 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
792 usec_t usa
= 0, usb
= 0;
793 struct unit_times
*times
;
795 times
= hashmap_get(unit_times_hashmap
, *a
);
797 usa
= times
->activated
;
798 times
= hashmap_get(unit_times_hashmap
, *b
);
800 usb
= times
->activated
;
805 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
806 unsigned int branches
) {
807 _cleanup_strv_free_
char **deps
= NULL
;
810 usec_t service_longest
= 0;
812 struct unit_times
*times
;
813 struct boot_times
*boot
;
815 if (strv_extend(units
, name
))
818 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
822 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
824 r
= acquire_boot_times(bus
, &boot
);
828 STRV_FOREACH(c
, deps
) {
829 times
= hashmap_get(unit_times_hashmap
, *c
);
832 && times
->activated
<= boot
->finish_time
833 && (times
->activated
>= service_longest
834 || service_longest
== 0)) {
835 service_longest
= times
->activated
;
840 if (service_longest
== 0 )
843 STRV_FOREACH(c
, deps
) {
844 times
= hashmap_get(unit_times_hashmap
, *c
);
845 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
852 STRV_FOREACH(c
, deps
) {
853 times
= hashmap_get(unit_times_hashmap
, *c
);
856 || times
->activated
> boot
->finish_time
857 || service_longest
- times
->activated
> arg_fuzz
)
862 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
866 if (strv_contains(*units
, *c
)) {
867 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
874 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
875 (branches
<< 1) | (to_print
? 1 : 0));
885 static int list_dependencies(sd_bus
*bus
, const char *name
) {
886 _cleanup_strv_free_
char **units
= NULL
;
887 char ts
[FORMAT_TIMESPAN_MAX
];
888 struct unit_times
*times
;
891 _cleanup_free_
char *path
= NULL
;
892 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
893 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
894 struct boot_times
*boot
;
898 path
= unit_dbus_path_from_name(name
);
902 r
= sd_bus_get_property(
904 "org.freedesktop.systemd1",
906 "org.freedesktop.systemd1.Unit",
912 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
916 r
= sd_bus_message_read(reply
, "s", &id
);
918 return bus_log_parse_error(r
);
920 times
= hashmap_get(unit_times_hashmap
, id
);
922 r
= acquire_boot_times(bus
, &boot
);
928 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED
, id
,
929 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
930 else if (times
->activated
> boot
->userspace_time
)
931 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
936 return list_dependencies_one(bus
, name
, 0, &units
, 0);
939 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
940 struct unit_times
*times
;
945 n
= acquire_time_data(bus
, ×
);
949 h
= hashmap_new(&string_hash_ops
);
953 for (i
= 0; i
< (unsigned)n
; i
++) {
954 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
958 unit_times_hashmap
= h
;
960 pager_open(arg_no_pager
, false);
962 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
963 "The time the unit takes to start is printed after the \"+\" character.\n");
965 if (!strv_isempty(names
)) {
967 STRV_FOREACH(name
, names
)
968 list_dependencies(bus
, *name
);
970 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
973 free_unit_times(times
, (unsigned) n
);
977 static int analyze_blame(sd_bus
*bus
) {
978 struct unit_times
*times
;
982 n
= acquire_time_data(bus
, ×
);
986 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
988 pager_open(arg_no_pager
, false);
990 for (i
= 0; i
< (unsigned) n
; i
++) {
991 char ts
[FORMAT_TIMESPAN_MAX
];
993 if (times
[i
].time
> 0)
994 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
997 free_unit_times(times
, (unsigned) n
);
1001 static int analyze_time(sd_bus
*bus
) {
1002 _cleanup_free_
char *buf
= NULL
;
1005 r
= pretty_boot_time(bus
, &buf
);
1013 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
[]) {
1014 _cleanup_strv_free_
char **units
= NULL
;
1017 bool match_patterns
;
1023 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1025 if (!strv_isempty(from_patterns
) &&
1027 !strv_fnmatch(from_patterns
, u
->id
, 0))
1030 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1034 STRV_FOREACH(unit
, units
) {
1035 bool match_patterns2
;
1037 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1039 if (!strv_isempty(to_patterns
) &&
1041 !strv_fnmatch(to_patterns
, *unit
, 0))
1044 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1047 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1053 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1059 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1060 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1065 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1066 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1069 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1072 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1075 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1083 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1084 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1088 STRV_FOREACH(pattern
, patterns
) {
1089 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1090 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1092 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1095 if (string_is_glob(*pattern
))
1098 unit
= unit_dbus_path_from_name(*pattern
);
1102 r
= sd_bus_get_property_string(
1104 "org.freedesktop.systemd1",
1106 "org.freedesktop.systemd1.Unit",
1111 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1113 if (!streq(*pattern
, unit_id
)) {
1114 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1119 *ret
= expanded_patterns
;
1120 expanded_patterns
= NULL
; /* do not free */
1125 static int dot(sd_bus
*bus
, char* patterns
[]) {
1126 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1127 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1128 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1129 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1130 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1134 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1138 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1142 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1146 r
= sd_bus_call_method(
1148 "org.freedesktop.systemd1",
1149 "/org/freedesktop/systemd1",
1150 "org.freedesktop.systemd1.Manager",
1156 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1160 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1162 return bus_log_parse_error(r
);
1164 printf("digraph systemd {\n");
1166 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1168 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1173 return bus_log_parse_error(r
);
1177 log_info(" Color legend: black = Requires\n"
1178 " dark blue = Requisite\n"
1179 " dark grey = Wants\n"
1180 " red = Conflicts\n"
1181 " green = After\n");
1184 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1185 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1190 static int dump(sd_bus
*bus
, char **args
) {
1191 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1192 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1193 const char *text
= NULL
;
1196 if (!strv_isempty(args
)) {
1197 log_error("Too many arguments.");
1201 pager_open(arg_no_pager
, false);
1203 r
= sd_bus_call_method(
1205 "org.freedesktop.systemd1",
1206 "/org/freedesktop/systemd1",
1207 "org.freedesktop.systemd1.Manager",
1213 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1215 r
= sd_bus_message_read(reply
, "s", &text
);
1217 return bus_log_parse_error(r
);
1219 fputs(text
, stdout
);
1223 static int set_log_level(sd_bus
*bus
, char **args
) {
1224 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1230 if (strv_length(args
) != 1) {
1231 log_error("This command expects one argument only.");
1235 r
= sd_bus_set_property(
1237 "org.freedesktop.systemd1",
1238 "/org/freedesktop/systemd1",
1239 "org.freedesktop.systemd1.Manager",
1245 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1250 static int set_log_target(sd_bus
*bus
, char **args
) {
1251 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1257 if (strv_length(args
) != 1) {
1258 log_error("This command expects one argument only.");
1262 r
= sd_bus_set_property(
1264 "org.freedesktop.systemd1",
1265 "/org/freedesktop/systemd1",
1266 "org.freedesktop.systemd1.Manager",
1272 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1277 static void help(void) {
1279 pager_open(arg_no_pager
, false);
1281 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1282 "Profile systemd, show unit dependencies, check unit files.\n\n"
1283 " -h --help Show this help\n"
1284 " --version Show package version\n"
1285 " --no-pager Do not pipe output into a pager\n"
1286 " --system Operate on system systemd instance\n"
1287 " --user Operate on user systemd instance\n"
1288 " -H --host=[USER@]HOST Operate on remote host\n"
1289 " -M --machine=CONTAINER Operate on local container\n"
1290 " --order Show only order in the graph\n"
1291 " --require Show only requirement in the graph\n"
1292 " --from-pattern=GLOB Show only origins in the graph\n"
1293 " --to-pattern=GLOB Show only destinations in the graph\n"
1294 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1295 " earlier than the latest in the branch\n"
1296 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1298 " time Print time spent in the kernel\n"
1299 " blame Print list of running units ordered by time to init\n"
1300 " critical-chain Print a tree of the time critical chain of units\n"
1301 " plot Output SVG graphic showing service initialization\n"
1302 " dot Output dependency graph in dot(1) format\n"
1303 " set-log-level LEVEL Set logging threshold for manager\n"
1304 " set-log-target TARGET Set logging target for manager\n"
1305 " dump Output state serialization of service manager\n"
1306 " verify FILE... Check unit files for correctness\n"
1307 , program_invocation_short_name
);
1309 /* When updating this list, including descriptions, apply
1310 * changes to shell-completion/bash/systemd-analyze and
1311 * shell-completion/zsh/_systemd-analyze too. */
1314 static int parse_argv(int argc
, char *argv
[]) {
1316 ARG_VERSION
= 0x100,
1321 ARG_DOT_FROM_PATTERN
,
1328 static const struct option options
[] = {
1329 { "help", no_argument
, NULL
, 'h' },
1330 { "version", no_argument
, NULL
, ARG_VERSION
},
1331 { "order", no_argument
, NULL
, ARG_ORDER
},
1332 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1333 { "user", no_argument
, NULL
, ARG_USER
},
1334 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1335 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1336 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1337 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1338 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1339 { "man", optional_argument
, NULL
, ARG_MAN
},
1340 { "host", required_argument
, NULL
, 'H' },
1341 { "machine", required_argument
, NULL
, 'M' },
1350 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1369 arg_dot
= DEP_ORDER
;
1373 arg_dot
= DEP_REQUIRE
;
1376 case ARG_DOT_FROM_PATTERN
:
1377 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1382 case ARG_DOT_TO_PATTERN
:
1383 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1389 r
= parse_sec(optarg
, &arg_fuzz
);
1395 arg_no_pager
= true;
1399 arg_transport
= BUS_TRANSPORT_REMOTE
;
1404 arg_transport
= BUS_TRANSPORT_MACHINE
;
1410 r
= parse_boolean(optarg
);
1412 log_error("Failed to parse --man= argument.");
1426 assert_not_reached("Unhandled option code.");
1429 return 1; /* work to do */
1432 int main(int argc
, char *argv
[]) {
1435 setlocale(LC_ALL
, "");
1436 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1437 log_parse_environment();
1440 r
= parse_argv(argc
, argv
);
1444 if (streq_ptr(argv
[optind
], "verify"))
1445 r
= verify_units(argv
+optind
+1,
1446 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1449 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1451 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1453 log_error_errno(r
, "Failed to create bus connection: %m");
1457 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1458 r
= analyze_time(bus
);
1459 else if (streq(argv
[optind
], "blame"))
1460 r
= analyze_blame(bus
);
1461 else if (streq(argv
[optind
], "critical-chain"))
1462 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1463 else if (streq(argv
[optind
], "plot"))
1464 r
= analyze_plot(bus
);
1465 else if (streq(argv
[optind
], "dot"))
1466 r
= dot(bus
, argv
+optind
+1);
1467 else if (streq(argv
[optind
], "dump"))
1468 r
= dump(bus
, argv
+optind
+1);
1469 else if (streq(argv
[optind
], "set-log-level"))
1470 r
= set_log_level(bus
, argv
+optind
+1);
1471 else if (streq(argv
[optind
], "set-log-target"))
1472 r
= set_log_target(bus
, argv
+optind
+1);
1474 log_error("Unknown operation '%s'.", argv
[optind
]);
1480 strv_free(arg_dot_from_patterns
);
1481 strv_free(arg_dot_to_patterns
);
1483 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;