1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010-2013 Lennart Poettering
7 Copyright 2013 Simon Peeters
9 systemd is free software; you can redistribute it and/or modify it
10 under the terms of the GNU Lesser General Public License as published by
11 the Free Software Foundation; either version 2.1 of the License, or
12 (at your option) any later version.
14 systemd is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Lesser General Public License for more details.
19 You should have received a copy of the GNU Lesser General Public License
20 along with systemd; If not, see <http://www.gnu.org/licenses/>.
30 #include "alloc-util.h"
31 #include "analyze-verify.h"
32 #include "bus-error.h"
34 #include "glob-util.h"
36 #include "locale-util.h"
39 #include "parse-util.h"
43 #include "terminal-util.h"
44 #include "unit-name.h"
47 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
48 #define SCALE_Y (20.0)
50 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
52 #define svg(...) printf(__VA_ARGS__)
54 #define svg_bar(class, x1, x2, y) \
55 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
57 SCALE_X * (x1), SCALE_Y * (y), \
58 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
60 #define svg_text(b, x, y, format, ...) \
62 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
63 svg(format, ## __VA_ARGS__); \
72 static char** arg_dot_from_patterns
= NULL
;
73 static char** arg_dot_to_patterns
= NULL
;
74 static usec_t arg_fuzz
= 0;
75 static bool arg_no_pager
= false;
76 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
77 static char *arg_host
= NULL
;
78 static bool arg_user
= false;
79 static bool arg_man
= true;
85 usec_t kernel_done_time
;
87 usec_t userspace_time
;
89 usec_t security_start_time
;
90 usec_t security_finish_time
;
91 usec_t generators_start_time
;
92 usec_t generators_finish_time
;
93 usec_t unitsload_start_time
;
94 usec_t unitsload_finish_time
;
97 * If we're analyzing the user instance, all timestamps will be offset
98 * by its own start-up timestamp, which may be arbitrarily big.
99 * With "plot", this causes arbitrarily wide output SVG files which almost
100 * completely consist of empty space. Thus we cancel out this offset.
102 * This offset is subtracted from times above by acquire_boot_times(),
103 * but it still needs to be subtracted from unit-specific timestamps
104 * (so it is stored here for reference).
106 usec_t reverse_offset
;
121 char *kernel_release
;
122 char *kernel_version
;
123 char *os_pretty_name
;
124 char *virtualization
;
128 static void pager_open_if_enabled(void) {
136 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
137 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
146 r
= sd_bus_get_property_trivial(
148 "org.freedesktop.systemd1",
156 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
163 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
164 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
172 r
= sd_bus_get_property_strv(
174 "org.freedesktop.systemd1",
176 "org.freedesktop.systemd1.Unit",
181 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
188 static int compare_unit_time(const void *a
, const void *b
) {
189 return compare(((struct unit_times
*)b
)->time
,
190 ((struct unit_times
*)a
)->time
);
193 static int compare_unit_start(const void *a
, const void *b
) {
194 return compare(((struct unit_times
*)a
)->activating
,
195 ((struct unit_times
*)b
)->activating
);
198 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
199 struct unit_times
*p
;
201 for (p
= t
; p
< t
+ n
; p
++)
207 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
216 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
217 static struct boot_times times
;
218 static bool cached
= false;
223 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
225 if (bus_get_uint64_property(bus
,
226 "/org/freedesktop/systemd1",
227 "org.freedesktop.systemd1.Manager",
228 "FirmwareTimestampMonotonic",
229 ×
.firmware_time
) < 0 ||
230 bus_get_uint64_property(bus
,
231 "/org/freedesktop/systemd1",
232 "org.freedesktop.systemd1.Manager",
233 "LoaderTimestampMonotonic",
234 ×
.loader_time
) < 0 ||
235 bus_get_uint64_property(bus
,
236 "/org/freedesktop/systemd1",
237 "org.freedesktop.systemd1.Manager",
239 ×
.kernel_time
) < 0 ||
240 bus_get_uint64_property(bus
,
241 "/org/freedesktop/systemd1",
242 "org.freedesktop.systemd1.Manager",
243 "InitRDTimestampMonotonic",
244 ×
.initrd_time
) < 0 ||
245 bus_get_uint64_property(bus
,
246 "/org/freedesktop/systemd1",
247 "org.freedesktop.systemd1.Manager",
248 "UserspaceTimestampMonotonic",
249 ×
.userspace_time
) < 0 ||
250 bus_get_uint64_property(bus
,
251 "/org/freedesktop/systemd1",
252 "org.freedesktop.systemd1.Manager",
253 "FinishTimestampMonotonic",
254 ×
.finish_time
) < 0 ||
255 bus_get_uint64_property(bus
,
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
258 "SecurityStartTimestampMonotonic",
259 ×
.security_start_time
) < 0 ||
260 bus_get_uint64_property(bus
,
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
263 "SecurityFinishTimestampMonotonic",
264 ×
.security_finish_time
) < 0 ||
265 bus_get_uint64_property(bus
,
266 "/org/freedesktop/systemd1",
267 "org.freedesktop.systemd1.Manager",
268 "GeneratorsStartTimestampMonotonic",
269 ×
.generators_start_time
) < 0 ||
270 bus_get_uint64_property(bus
,
271 "/org/freedesktop/systemd1",
272 "org.freedesktop.systemd1.Manager",
273 "GeneratorsFinishTimestampMonotonic",
274 ×
.generators_finish_time
) < 0 ||
275 bus_get_uint64_property(bus
,
276 "/org/freedesktop/systemd1",
277 "org.freedesktop.systemd1.Manager",
278 "UnitsLoadStartTimestampMonotonic",
279 ×
.unitsload_start_time
) < 0 ||
280 bus_get_uint64_property(bus
,
281 "/org/freedesktop/systemd1",
282 "org.freedesktop.systemd1.Manager",
283 "UnitsLoadFinishTimestampMonotonic",
284 ×
.unitsload_finish_time
) < 0)
287 if (times
.finish_time
<= 0) {
288 log_error("Bootup is not yet finished. Please try again later.");
294 * User-instance-specific timestamps processing
295 * (see comment to reverse_offset in struct boot_times).
297 times
.reverse_offset
= times
.userspace_time
;
299 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
300 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
302 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
303 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
305 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
306 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
308 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
309 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
311 if (times
.initrd_time
)
312 times
.kernel_done_time
= times
.initrd_time
;
314 times
.kernel_done_time
= times
.userspace_time
;
324 static void free_host_info(struct host_info
*hi
) {
330 free(hi
->kernel_name
);
331 free(hi
->kernel_release
);
332 free(hi
->kernel_version
);
333 free(hi
->os_pretty_name
);
334 free(hi
->virtualization
);
335 free(hi
->architecture
);
339 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
341 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
342 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
343 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
345 struct boot_times
*boot_times
= NULL
;
346 struct unit_times
*unit_times
= NULL
;
350 r
= acquire_boot_times(bus
, &boot_times
);
354 r
= sd_bus_call_method(
356 "org.freedesktop.systemd1",
357 "/org/freedesktop/systemd1",
358 "org.freedesktop.systemd1.Manager",
363 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
367 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
369 bus_log_parse_error(r
);
373 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
374 struct unit_times
*t
;
376 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
384 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
386 if (bus_get_uint64_property(bus
, u
.unit_path
,
387 "org.freedesktop.systemd1.Unit",
388 "InactiveExitTimestampMonotonic",
389 &t
->activating
) < 0 ||
390 bus_get_uint64_property(bus
, u
.unit_path
,
391 "org.freedesktop.systemd1.Unit",
392 "ActiveEnterTimestampMonotonic",
393 &t
->activated
) < 0 ||
394 bus_get_uint64_property(bus
, u
.unit_path
,
395 "org.freedesktop.systemd1.Unit",
396 "ActiveExitTimestampMonotonic",
397 &t
->deactivating
) < 0 ||
398 bus_get_uint64_property(bus
, u
.unit_path
,
399 "org.freedesktop.systemd1.Unit",
400 "InactiveEnterTimestampMonotonic",
401 &t
->deactivated
) < 0) {
406 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
407 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
408 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
409 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
411 if (t
->activated
>= t
->activating
)
412 t
->time
= t
->activated
- t
->activating
;
413 else if (t
->deactivated
>= t
->activating
)
414 t
->time
= t
->deactivated
- t
->activating
;
418 if (t
->activating
== 0)
421 t
->name
= strdup(u
.id
);
422 if (t
->name
== NULL
) {
429 bus_log_parse_error(r
);
438 free_unit_times(unit_times
, (unsigned) c
);
442 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
443 static const struct bus_properties_map hostname_map
[] = {
444 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
445 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
446 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
447 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
448 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
452 static const struct bus_properties_map manager_map
[] = {
453 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
454 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
458 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
459 _cleanup_(free_host_infop
) struct host_info
*host
;
462 host
= new0(struct host_info
, 1);
466 r
= bus_map_all_properties(bus
,
467 "org.freedesktop.hostname1",
468 "/org/freedesktop/hostname1",
472 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
474 r
= bus_map_all_properties(bus
,
475 "org.freedesktop.systemd1",
476 "/org/freedesktop/systemd1",
480 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
488 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
489 char ts
[FORMAT_TIMESPAN_MAX
];
490 struct boot_times
*t
;
491 static char buf
[4096];
496 r
= acquire_boot_times(bus
, &t
);
503 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
504 if (t
->firmware_time
)
505 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
507 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
509 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
510 if (t
->initrd_time
> 0)
511 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
513 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
514 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
524 static void svg_graph_box(double height
, double begin
, double end
) {
527 /* outside box, fill */
528 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
529 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
531 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
532 /* lines for each second */
533 if (i
% 5000000 == 0)
534 svg(" <line class=\"sec5\" 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
);
537 else if (i
% 1000000 == 0)
538 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
539 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
540 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
542 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
543 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
547 static int analyze_plot(sd_bus
*bus
) {
548 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
549 struct unit_times
*times
;
550 struct boot_times
*boot
;
553 _cleanup_free_
char *pretty_times
= NULL
;
554 struct unit_times
*u
;
556 n
= acquire_boot_times(bus
, &boot
);
560 n
= pretty_boot_time(bus
, &pretty_times
);
564 n
= acquire_host_info(bus
, &host
);
568 n
= acquire_time_data(bus
, ×
);
572 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
574 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
578 if (boot
->firmware_time
> boot
->loader_time
)
580 if (boot
->loader_time
) {
585 if (boot
->initrd_time
)
587 if (boot
->kernel_time
)
590 for (u
= times
; u
< times
+ n
; u
++) {
591 double text_start
, text_width
;
593 if (u
->activating
< boot
->userspace_time
||
594 u
->activating
> boot
->finish_time
) {
595 u
->name
= mfree(u
->name
);
599 /* If the text cannot fit on the left side then
600 * increase the svg width so it fits on the right.
601 * TODO: calculate the text width more accurately */
602 text_width
= 8.0 * strlen(u
->name
);
603 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
604 if (text_width
> text_start
&& text_width
+ text_start
> width
)
605 width
= text_width
+ text_start
;
607 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
608 && u
->activated
== 0 && u
->deactivating
== 0)
609 u
->activated
= u
->deactivating
= u
->deactivated
;
610 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
611 u
->activated
= boot
->finish_time
;
612 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
613 u
->deactivating
= boot
->finish_time
;
614 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
615 u
->deactivated
= boot
->finish_time
;
619 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
620 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
621 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
623 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
624 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
625 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
626 5 * SCALE_Y
/* legend */);
628 /* write some basic info as a comment, including some help */
629 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
630 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
631 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
632 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
633 "<!-- point your browser to this file. -->\n\n"
634 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
637 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
638 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
639 " rect.background { fill: rgb(255,255,255); }\n"
640 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
641 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
642 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
643 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
644 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
645 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
646 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
647 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
648 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
649 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
650 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
651 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
652 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
654 " line.sec5 { stroke-width: 2; }\n"
655 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
656 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
657 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
658 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
659 " text.sec { font-size: 10px; }\n"
660 " ]]>\n </style>\n</defs>\n\n");
662 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
663 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
664 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
665 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
666 strempty(host
->hostname
),
667 strempty(host
->kernel_name
),
668 strempty(host
->kernel_release
),
669 strempty(host
->kernel_version
),
670 strempty(host
->architecture
),
671 strempty(host
->virtualization
));
673 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
674 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
676 if (boot
->firmware_time
) {
677 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
678 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
681 if (boot
->loader_time
) {
682 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
683 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
686 if (boot
->kernel_time
) {
687 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
688 svg_text(true, 0, y
, "kernel");
691 if (boot
->initrd_time
) {
692 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
693 svg_text(true, boot
->initrd_time
, y
, "initrd");
696 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
697 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
698 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
699 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
700 svg_text(true, boot
->userspace_time
, y
, "systemd");
703 for (u
= times
; u
< times
+ n
; u
++) {
704 char ts
[FORMAT_TIMESPAN_MAX
];
710 svg_bar("activating", u
->activating
, u
->activated
, y
);
711 svg_bar("active", u
->activated
, u
->deactivating
, y
);
712 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
714 /* place the text on the left if we have passed the half of the svg width */
715 b
= u
->activating
* SCALE_X
< width
/ 2;
717 svg_text(b
, u
->activating
, y
, "%s (%s)",
718 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
720 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
727 svg("<g transform=\"translate(20,100)\">\n");
729 svg_bar("activating", 0, 300000, y
);
730 svg_text(true, 400000, y
, "Activating");
732 svg_bar("active", 0, 300000, y
);
733 svg_text(true, 400000, y
, "Active");
735 svg_bar("deactivating", 0, 300000, y
);
736 svg_text(true, 400000, y
, "Deactivating");
738 svg_bar("security", 0, 300000, y
);
739 svg_text(true, 400000, y
, "Setting up security module");
741 svg_bar("generators", 0, 300000, y
);
742 svg_text(true, 400000, y
, "Generators");
744 svg_bar("unitsload", 0, 300000, y
);
745 svg_text(true, 400000, y
, "Loading unit files");
752 free_unit_times(times
, (unsigned) n
);
758 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
759 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
761 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
763 for (i
= level
; i
!= 0; i
--)
764 printf("%s", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
766 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_TREE_BRANCH
));
770 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED
, name
,
771 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
772 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
773 else if (times
->activated
> boot
->userspace_time
)
774 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
784 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
785 _cleanup_free_
char *path
= NULL
;
791 path
= unit_dbus_path_from_name(name
);
795 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
798 static Hashmap
*unit_times_hashmap
;
800 static int list_dependencies_compare(const void *_a
, const void *_b
) {
801 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
802 usec_t usa
= 0, usb
= 0;
803 struct unit_times
*times
;
805 times
= hashmap_get(unit_times_hashmap
, *a
);
807 usa
= times
->activated
;
808 times
= hashmap_get(unit_times_hashmap
, *b
);
810 usb
= times
->activated
;
815 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
816 unsigned int branches
) {
817 _cleanup_strv_free_
char **deps
= NULL
;
820 usec_t service_longest
= 0;
822 struct unit_times
*times
;
823 struct boot_times
*boot
;
825 if (strv_extend(units
, name
))
828 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
832 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
834 r
= acquire_boot_times(bus
, &boot
);
838 STRV_FOREACH(c
, deps
) {
839 times
= hashmap_get(unit_times_hashmap
, *c
);
842 && times
->activated
<= boot
->finish_time
843 && (times
->activated
>= service_longest
844 || service_longest
== 0)) {
845 service_longest
= times
->activated
;
850 if (service_longest
== 0 )
853 STRV_FOREACH(c
, deps
) {
854 times
= hashmap_get(unit_times_hashmap
, *c
);
855 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
862 STRV_FOREACH(c
, deps
) {
863 times
= hashmap_get(unit_times_hashmap
, *c
);
866 || times
->activated
> boot
->finish_time
867 || service_longest
- times
->activated
> arg_fuzz
)
872 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
876 if (strv_contains(*units
, *c
)) {
877 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
884 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
885 (branches
<< 1) | (to_print
? 1 : 0));
895 static int list_dependencies(sd_bus
*bus
, const char *name
) {
896 _cleanup_strv_free_
char **units
= NULL
;
897 char ts
[FORMAT_TIMESPAN_MAX
];
898 struct unit_times
*times
;
901 _cleanup_free_
char *path
= NULL
;
902 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
903 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
904 struct boot_times
*boot
;
908 path
= unit_dbus_path_from_name(name
);
912 r
= sd_bus_get_property(
914 "org.freedesktop.systemd1",
916 "org.freedesktop.systemd1.Unit",
922 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
926 r
= sd_bus_message_read(reply
, "s", &id
);
928 return bus_log_parse_error(r
);
930 times
= hashmap_get(unit_times_hashmap
, id
);
932 r
= acquire_boot_times(bus
, &boot
);
938 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED
, id
,
939 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
940 else if (times
->activated
> boot
->userspace_time
)
941 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
946 return list_dependencies_one(bus
, name
, 0, &units
, 0);
949 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
950 struct unit_times
*times
;
955 n
= acquire_time_data(bus
, ×
);
959 h
= hashmap_new(&string_hash_ops
);
963 for (i
= 0; i
< (unsigned)n
; i
++) {
964 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
968 unit_times_hashmap
= h
;
970 pager_open_if_enabled();
972 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
973 "The time the unit takes to start is printed after the \"+\" character.\n");
975 if (!strv_isempty(names
)) {
977 STRV_FOREACH(name
, names
)
978 list_dependencies(bus
, *name
);
980 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
983 free_unit_times(times
, (unsigned) n
);
987 static int analyze_blame(sd_bus
*bus
) {
988 struct unit_times
*times
;
992 n
= acquire_time_data(bus
, ×
);
996 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
998 pager_open_if_enabled();
1000 for (i
= 0; i
< (unsigned) n
; i
++) {
1001 char ts
[FORMAT_TIMESPAN_MAX
];
1003 if (times
[i
].time
> 0)
1004 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1007 free_unit_times(times
, (unsigned) n
);
1011 static int analyze_time(sd_bus
*bus
) {
1012 _cleanup_free_
char *buf
= NULL
;
1015 r
= pretty_boot_time(bus
, &buf
);
1023 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
[]) {
1024 _cleanup_strv_free_
char **units
= NULL
;
1027 bool match_patterns
;
1033 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1035 if (!strv_isempty(from_patterns
) &&
1037 !strv_fnmatch(from_patterns
, u
->id
, 0))
1040 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1044 STRV_FOREACH(unit
, units
) {
1045 bool match_patterns2
;
1047 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1049 if (!strv_isempty(to_patterns
) &&
1051 !strv_fnmatch(to_patterns
, *unit
, 0))
1054 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1057 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1063 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1069 if (arg_dot
== DEP_ORDER
||arg_dot
== DEP_ALL
) {
1070 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1075 if (arg_dot
== DEP_REQUIRE
||arg_dot
== DEP_ALL
) {
1076 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1079 r
= graph_one_property(bus
, u
, "RequiresOverridable", "black", patterns
, from_patterns
, to_patterns
);
1082 r
= graph_one_property(bus
, u
, "RequisiteOverridable", "darkblue", patterns
, from_patterns
, to_patterns
);
1085 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1088 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1091 r
= graph_one_property(bus
, u
, "ConflictedBy", "red", patterns
, from_patterns
, to_patterns
);
1099 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1100 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1104 STRV_FOREACH(pattern
, patterns
) {
1105 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1106 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1108 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1111 if (string_is_glob(*pattern
))
1114 unit
= unit_dbus_path_from_name(*pattern
);
1118 r
= sd_bus_get_property_string(
1120 "org.freedesktop.systemd1",
1122 "org.freedesktop.systemd1.Unit",
1127 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1129 if (!streq(*pattern
, unit_id
)) {
1130 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1135 *ret
= expanded_patterns
;
1136 expanded_patterns
= NULL
; /* do not free */
1141 static int dot(sd_bus
*bus
, char* patterns
[]) {
1142 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1143 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1144 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1145 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1146 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1150 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1154 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1158 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1162 r
= sd_bus_call_method(
1164 "org.freedesktop.systemd1",
1165 "/org/freedesktop/systemd1",
1166 "org.freedesktop.systemd1.Manager",
1172 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1176 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1178 return bus_log_parse_error(r
);
1180 printf("digraph systemd {\n");
1182 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1184 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1189 return bus_log_parse_error(r
);
1193 log_info(" Color legend: black = Requires\n"
1194 " dark blue = Requisite\n"
1195 " dark grey = Wants\n"
1196 " red = Conflicts\n"
1197 " green = After\n");
1200 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1201 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1206 static int dump(sd_bus
*bus
, char **args
) {
1207 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1208 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1209 const char *text
= NULL
;
1212 if (!strv_isempty(args
)) {
1213 log_error("Too many arguments.");
1217 pager_open_if_enabled();
1219 r
= sd_bus_call_method(
1221 "org.freedesktop.systemd1",
1222 "/org/freedesktop/systemd1",
1223 "org.freedesktop.systemd1.Manager",
1229 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1231 r
= sd_bus_message_read(reply
, "s", &text
);
1233 return bus_log_parse_error(r
);
1235 fputs(text
, stdout
);
1239 static int set_log_level(sd_bus
*bus
, char **args
) {
1240 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1246 if (strv_length(args
) != 1) {
1247 log_error("This command expects one argument only.");
1251 r
= sd_bus_set_property(
1253 "org.freedesktop.systemd1",
1254 "/org/freedesktop/systemd1",
1255 "org.freedesktop.systemd1.Manager",
1261 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1266 static int set_log_target(sd_bus
*bus
, char **args
) {
1267 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1273 if (strv_length(args
) != 1) {
1274 log_error("This command expects one argument only.");
1278 r
= sd_bus_set_property(
1280 "org.freedesktop.systemd1",
1281 "/org/freedesktop/systemd1",
1282 "org.freedesktop.systemd1.Manager",
1288 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1293 static void help(void) {
1295 pager_open_if_enabled();
1297 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1298 "Profile systemd, show unit dependencies, check unit files.\n\n"
1299 " -h --help Show this help\n"
1300 " --version Show package version\n"
1301 " --no-pager Do not pipe output into a pager\n"
1302 " --system Operate on system systemd instance\n"
1303 " --user Operate on user systemd instance\n"
1304 " -H --host=[USER@]HOST Operate on remote host\n"
1305 " -M --machine=CONTAINER Operate on local container\n"
1306 " --order Show only order in the graph\n"
1307 " --require Show only requirement in the graph\n"
1308 " --from-pattern=GLOB Show only origins in the graph\n"
1309 " --to-pattern=GLOB Show only destinations in the graph\n"
1310 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1311 " earlier than the latest in the branch\n"
1312 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1314 " time Print time spent in the kernel\n"
1315 " blame Print list of running units ordered by time to init\n"
1316 " critical-chain Print a tree of the time critical chain of units\n"
1317 " plot Output SVG graphic showing service initialization\n"
1318 " dot Output dependency graph in dot(1) format\n"
1319 " set-log-level LEVEL Set logging threshold for manager\n"
1320 " set-log-target TARGET Set logging target for manager\n"
1321 " dump Output state serialization of service manager\n"
1322 " verify FILE... Check unit files for correctness\n"
1323 , program_invocation_short_name
);
1325 /* When updating this list, including descriptions, apply
1326 * changes to shell-completion/bash/systemd-analyze and
1327 * shell-completion/zsh/_systemd-analyze too. */
1330 static int parse_argv(int argc
, char *argv
[]) {
1332 ARG_VERSION
= 0x100,
1337 ARG_DOT_FROM_PATTERN
,
1344 static const struct option options
[] = {
1345 { "help", no_argument
, NULL
, 'h' },
1346 { "version", no_argument
, NULL
, ARG_VERSION
},
1347 { "order", no_argument
, NULL
, ARG_ORDER
},
1348 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1349 { "user", no_argument
, NULL
, ARG_USER
},
1350 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1351 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1352 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1353 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1354 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1355 { "man", optional_argument
, NULL
, ARG_MAN
},
1356 { "host", required_argument
, NULL
, 'H' },
1357 { "machine", required_argument
, NULL
, 'M' },
1366 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1385 arg_dot
= DEP_ORDER
;
1389 arg_dot
= DEP_REQUIRE
;
1392 case ARG_DOT_FROM_PATTERN
:
1393 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1398 case ARG_DOT_TO_PATTERN
:
1399 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1405 r
= parse_sec(optarg
, &arg_fuzz
);
1411 arg_no_pager
= true;
1415 arg_transport
= BUS_TRANSPORT_REMOTE
;
1420 arg_transport
= BUS_TRANSPORT_MACHINE
;
1426 r
= parse_boolean(optarg
);
1428 log_error("Failed to parse --man= argument.");
1442 assert_not_reached("Unhandled option code.");
1445 return 1; /* work to do */
1448 int main(int argc
, char *argv
[]) {
1451 setlocale(LC_ALL
, "");
1452 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1453 log_parse_environment();
1456 r
= parse_argv(argc
, argv
);
1460 if (streq_ptr(argv
[optind
], "verify"))
1461 r
= verify_units(argv
+optind
+1,
1462 arg_user
? MANAGER_USER
: MANAGER_SYSTEM
,
1465 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1467 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1469 log_error_errno(r
, "Failed to create bus connection: %m");
1473 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1474 r
= analyze_time(bus
);
1475 else if (streq(argv
[optind
], "blame"))
1476 r
= analyze_blame(bus
);
1477 else if (streq(argv
[optind
], "critical-chain"))
1478 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1479 else if (streq(argv
[optind
], "plot"))
1480 r
= analyze_plot(bus
);
1481 else if (streq(argv
[optind
], "dot"))
1482 r
= dot(bus
, argv
+optind
+1);
1483 else if (streq(argv
[optind
], "dump"))
1484 r
= dump(bus
, argv
+optind
+1);
1485 else if (streq(argv
[optind
], "set-log-level"))
1486 r
= set_log_level(bus
, argv
+optind
+1);
1487 else if (streq(argv
[optind
], "set-log-target"))
1488 r
= set_log_target(bus
, argv
+optind
+1);
1490 log_error("Unknown operation '%s'.", argv
[optind
]);
1496 strv_free(arg_dot_from_patterns
);
1497 strv_free(arg_dot_to_patterns
);
1499 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;