2 This file is part of systemd.
4 Copyright 2010-2013 Lennart Poettering
5 Copyright 2013 Simon Peeters
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "alloc-util.h"
29 #include "analyze-verify.h"
30 #include "bus-error.h"
31 #include "bus-unit-util.h"
33 #include "glob-util.h"
35 #include "locale-util.h"
38 #include "parse-util.h"
42 #include "terminal-util.h"
43 #include "unit-name.h"
46 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
47 #define SCALE_Y (20.0)
49 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
51 #define svg(...) printf(__VA_ARGS__)
53 #define svg_bar(class, x1, x2, y) \
54 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
56 SCALE_X * (x1), SCALE_Y * (y), \
57 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
59 #define svg_text(b, x, y, format, ...) \
61 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
62 svg(format, ## __VA_ARGS__); \
71 static char** arg_dot_from_patterns
= NULL
;
72 static char** arg_dot_to_patterns
= NULL
;
73 static usec_t arg_fuzz
= 0;
74 static bool arg_no_pager
= false;
75 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
76 static char *arg_host
= NULL
;
77 static bool arg_user
= false;
78 static bool arg_man
= true;
84 usec_t kernel_done_time
;
86 usec_t userspace_time
;
88 usec_t security_start_time
;
89 usec_t security_finish_time
;
90 usec_t generators_start_time
;
91 usec_t generators_finish_time
;
92 usec_t unitsload_start_time
;
93 usec_t unitsload_finish_time
;
96 * If we're analyzing the user instance, all timestamps will be offset
97 * by its own start-up timestamp, which may be arbitrarily big.
98 * With "plot", this causes arbitrarily wide output SVG files which almost
99 * completely consist of empty space. Thus we cancel out this offset.
101 * This offset is subtracted from times above by acquire_boot_times(),
102 * but it still needs to be subtracted from unit-specific timestamps
103 * (so it is stored here for reference).
105 usec_t reverse_offset
;
120 char *kernel_release
;
121 char *kernel_version
;
122 char *os_pretty_name
;
123 char *virtualization
;
127 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
128 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
137 r
= sd_bus_get_property_trivial(
139 "org.freedesktop.systemd1",
147 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
154 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
155 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
163 r
= sd_bus_get_property_strv(
165 "org.freedesktop.systemd1",
167 "org.freedesktop.systemd1.Unit",
172 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
179 static int compare_unit_time(const void *a
, const void *b
) {
180 return compare(((struct unit_times
*)b
)->time
,
181 ((struct unit_times
*)a
)->time
);
184 static int compare_unit_start(const void *a
, const void *b
) {
185 return compare(((struct unit_times
*)a
)->activating
,
186 ((struct unit_times
*)b
)->activating
);
189 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
190 struct unit_times
*p
;
192 for (p
= t
; p
< t
+ n
; p
++)
198 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
207 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
208 static struct boot_times times
;
209 static bool cached
= false;
214 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
216 if (bus_get_uint64_property(bus
,
217 "/org/freedesktop/systemd1",
218 "org.freedesktop.systemd1.Manager",
219 "FirmwareTimestampMonotonic",
220 ×
.firmware_time
) < 0 ||
221 bus_get_uint64_property(bus
,
222 "/org/freedesktop/systemd1",
223 "org.freedesktop.systemd1.Manager",
224 "LoaderTimestampMonotonic",
225 ×
.loader_time
) < 0 ||
226 bus_get_uint64_property(bus
,
227 "/org/freedesktop/systemd1",
228 "org.freedesktop.systemd1.Manager",
230 ×
.kernel_time
) < 0 ||
231 bus_get_uint64_property(bus
,
232 "/org/freedesktop/systemd1",
233 "org.freedesktop.systemd1.Manager",
234 "InitRDTimestampMonotonic",
235 ×
.initrd_time
) < 0 ||
236 bus_get_uint64_property(bus
,
237 "/org/freedesktop/systemd1",
238 "org.freedesktop.systemd1.Manager",
239 "UserspaceTimestampMonotonic",
240 ×
.userspace_time
) < 0 ||
241 bus_get_uint64_property(bus
,
242 "/org/freedesktop/systemd1",
243 "org.freedesktop.systemd1.Manager",
244 "FinishTimestampMonotonic",
245 ×
.finish_time
) < 0 ||
246 bus_get_uint64_property(bus
,
247 "/org/freedesktop/systemd1",
248 "org.freedesktop.systemd1.Manager",
249 "SecurityStartTimestampMonotonic",
250 ×
.security_start_time
) < 0 ||
251 bus_get_uint64_property(bus
,
252 "/org/freedesktop/systemd1",
253 "org.freedesktop.systemd1.Manager",
254 "SecurityFinishTimestampMonotonic",
255 ×
.security_finish_time
) < 0 ||
256 bus_get_uint64_property(bus
,
257 "/org/freedesktop/systemd1",
258 "org.freedesktop.systemd1.Manager",
259 "GeneratorsStartTimestampMonotonic",
260 ×
.generators_start_time
) < 0 ||
261 bus_get_uint64_property(bus
,
262 "/org/freedesktop/systemd1",
263 "org.freedesktop.systemd1.Manager",
264 "GeneratorsFinishTimestampMonotonic",
265 ×
.generators_finish_time
) < 0 ||
266 bus_get_uint64_property(bus
,
267 "/org/freedesktop/systemd1",
268 "org.freedesktop.systemd1.Manager",
269 "UnitsLoadStartTimestampMonotonic",
270 ×
.unitsload_start_time
) < 0 ||
271 bus_get_uint64_property(bus
,
272 "/org/freedesktop/systemd1",
273 "org.freedesktop.systemd1.Manager",
274 "UnitsLoadFinishTimestampMonotonic",
275 ×
.unitsload_finish_time
) < 0)
278 if (times
.finish_time
<= 0) {
279 log_error("Bootup is not yet finished. Please try again later.");
285 * User-instance-specific timestamps processing
286 * (see comment to reverse_offset in struct boot_times).
288 times
.reverse_offset
= times
.userspace_time
;
290 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
291 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
293 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
294 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
296 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
297 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
302 if (times
.initrd_time
)
303 times
.kernel_done_time
= times
.initrd_time
;
305 times
.kernel_done_time
= times
.userspace_time
;
315 static void free_host_info(struct host_info
*hi
) {
321 free(hi
->kernel_name
);
322 free(hi
->kernel_release
);
323 free(hi
->kernel_version
);
324 free(hi
->os_pretty_name
);
325 free(hi
->virtualization
);
326 free(hi
->architecture
);
330 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
332 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
333 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
334 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
336 struct boot_times
*boot_times
= NULL
;
337 struct unit_times
*unit_times
= NULL
;
341 r
= acquire_boot_times(bus
, &boot_times
);
345 r
= sd_bus_call_method(
347 "org.freedesktop.systemd1",
348 "/org/freedesktop/systemd1",
349 "org.freedesktop.systemd1.Manager",
354 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
358 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
360 bus_log_parse_error(r
);
364 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
365 struct unit_times
*t
;
367 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
375 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
377 if (bus_get_uint64_property(bus
, u
.unit_path
,
378 "org.freedesktop.systemd1.Unit",
379 "InactiveExitTimestampMonotonic",
380 &t
->activating
) < 0 ||
381 bus_get_uint64_property(bus
, u
.unit_path
,
382 "org.freedesktop.systemd1.Unit",
383 "ActiveEnterTimestampMonotonic",
384 &t
->activated
) < 0 ||
385 bus_get_uint64_property(bus
, u
.unit_path
,
386 "org.freedesktop.systemd1.Unit",
387 "ActiveExitTimestampMonotonic",
388 &t
->deactivating
) < 0 ||
389 bus_get_uint64_property(bus
, u
.unit_path
,
390 "org.freedesktop.systemd1.Unit",
391 "InactiveEnterTimestampMonotonic",
392 &t
->deactivated
) < 0) {
397 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
398 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
399 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
400 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
402 if (t
->activated
>= t
->activating
)
403 t
->time
= t
->activated
- t
->activating
;
404 else if (t
->deactivated
>= t
->activating
)
405 t
->time
= t
->deactivated
- t
->activating
;
409 if (t
->activating
== 0)
412 t
->name
= strdup(u
.id
);
413 if (t
->name
== NULL
) {
420 bus_log_parse_error(r
);
429 free_unit_times(unit_times
, (unsigned) c
);
433 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
434 static const struct bus_properties_map hostname_map
[] = {
435 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
436 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
437 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
438 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
439 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
443 static const struct bus_properties_map manager_map
[] = {
444 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
445 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
449 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
450 _cleanup_(free_host_infop
) struct host_info
*host
;
453 host
= new0(struct host_info
, 1);
457 r
= bus_map_all_properties(bus
,
458 "org.freedesktop.hostname1",
459 "/org/freedesktop/hostname1",
463 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
465 r
= bus_map_all_properties(bus
,
466 "org.freedesktop.systemd1",
467 "/org/freedesktop/systemd1",
471 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
479 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
480 char ts
[FORMAT_TIMESPAN_MAX
];
481 struct boot_times
*t
;
482 static char buf
[4096];
487 r
= acquire_boot_times(bus
, &t
);
494 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
495 if (t
->firmware_time
)
496 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
498 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
500 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
501 if (t
->initrd_time
> 0)
502 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
504 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
505 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
515 static void svg_graph_box(double height
, double begin
, double end
) {
518 /* outside box, fill */
519 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
520 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
522 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
523 /* lines for each second */
524 if (i
% 5000000 == 0)
525 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
526 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
527 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
528 else if (i
% 1000000 == 0)
529 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
530 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
531 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
533 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
534 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
538 static int analyze_plot(sd_bus
*bus
) {
539 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
540 struct unit_times
*times
;
541 struct boot_times
*boot
;
544 _cleanup_free_
char *pretty_times
= NULL
;
545 struct unit_times
*u
;
547 n
= acquire_boot_times(bus
, &boot
);
551 n
= pretty_boot_time(bus
, &pretty_times
);
555 n
= acquire_host_info(bus
, &host
);
559 n
= acquire_time_data(bus
, ×
);
563 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
565 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
569 if (boot
->firmware_time
> boot
->loader_time
)
571 if (boot
->loader_time
) {
576 if (boot
->initrd_time
)
578 if (boot
->kernel_time
)
581 for (u
= times
; u
< times
+ n
; u
++) {
582 double text_start
, text_width
;
584 if (u
->activating
< boot
->userspace_time
||
585 u
->activating
> boot
->finish_time
) {
586 u
->name
= mfree(u
->name
);
590 /* If the text cannot fit on the left side then
591 * increase the svg width so it fits on the right.
592 * TODO: calculate the text width more accurately */
593 text_width
= 8.0 * strlen(u
->name
);
594 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
595 if (text_width
> text_start
&& text_width
+ text_start
> width
)
596 width
= text_width
+ text_start
;
598 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
599 && u
->activated
== 0 && u
->deactivating
== 0)
600 u
->activated
= u
->deactivating
= u
->deactivated
;
601 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
602 u
->activated
= boot
->finish_time
;
603 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
604 u
->deactivating
= boot
->finish_time
;
605 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
606 u
->deactivated
= boot
->finish_time
;
610 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
611 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
612 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
614 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
615 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
616 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
617 5 * SCALE_Y
/* legend */);
619 /* write some basic info as a comment, including some help */
620 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
621 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
622 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
623 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
624 "<!-- point your browser to this file. -->\n\n"
625 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
628 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
629 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
630 " rect.background { fill: rgb(255,255,255); }\n"
631 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
632 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
633 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
634 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
635 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
636 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
637 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
638 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
639 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
640 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
641 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
642 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
643 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
645 " line.sec5 { stroke-width: 2; }\n"
646 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
647 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
648 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
649 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
650 " text.sec { font-size: 10px; }\n"
651 " ]]>\n </style>\n</defs>\n\n");
653 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
654 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
655 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
656 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
657 strempty(host
->hostname
),
658 strempty(host
->kernel_name
),
659 strempty(host
->kernel_release
),
660 strempty(host
->kernel_version
),
661 strempty(host
->architecture
),
662 strempty(host
->virtualization
));
664 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
665 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
667 if (boot
->firmware_time
) {
668 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
669 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
672 if (boot
->loader_time
) {
673 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
674 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
677 if (boot
->kernel_time
) {
678 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
679 svg_text(true, 0, y
, "kernel");
682 if (boot
->initrd_time
) {
683 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
684 svg_text(true, boot
->initrd_time
, y
, "initrd");
687 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
688 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
689 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
690 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
691 svg_text(true, boot
->userspace_time
, y
, "systemd");
694 for (u
= times
; u
< times
+ n
; u
++) {
695 char ts
[FORMAT_TIMESPAN_MAX
];
701 svg_bar("activating", u
->activating
, u
->activated
, y
);
702 svg_bar("active", u
->activated
, u
->deactivating
, y
);
703 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
705 /* place the text on the left if we have passed the half of the svg width */
706 b
= u
->activating
* SCALE_X
< width
/ 2;
708 svg_text(b
, u
->activating
, y
, "%s (%s)",
709 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
711 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
718 svg("<g transform=\"translate(20,100)\">\n");
720 svg_bar("activating", 0, 300000, y
);
721 svg_text(true, 400000, y
, "Activating");
723 svg_bar("active", 0, 300000, y
);
724 svg_text(true, 400000, y
, "Active");
726 svg_bar("deactivating", 0, 300000, y
);
727 svg_text(true, 400000, y
, "Deactivating");
729 svg_bar("security", 0, 300000, y
);
730 svg_text(true, 400000, y
, "Setting up security module");
732 svg_bar("generators", 0, 300000, y
);
733 svg_text(true, 400000, y
, "Generators");
735 svg_bar("unitsload", 0, 300000, y
);
736 svg_text(true, 400000, y
, "Loading unit files");
743 free_unit_times(times
, (unsigned) n
);
749 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
750 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
752 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
754 for (i
= level
; i
!= 0; i
--)
755 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
757 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
761 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED
, name
,
762 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
763 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
764 else if (times
->activated
> boot
->userspace_time
)
765 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
775 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
776 _cleanup_free_
char *path
= NULL
;
782 path
= unit_dbus_path_from_name(name
);
786 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
789 static Hashmap
*unit_times_hashmap
;
791 static int list_dependencies_compare(const void *_a
, const void *_b
) {
792 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
793 usec_t usa
= 0, usb
= 0;
794 struct unit_times
*times
;
796 times
= hashmap_get(unit_times_hashmap
, *a
);
798 usa
= times
->activated
;
799 times
= hashmap_get(unit_times_hashmap
, *b
);
801 usb
= times
->activated
;
806 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
807 unsigned int branches
) {
808 _cleanup_strv_free_
char **deps
= NULL
;
811 usec_t service_longest
= 0;
813 struct unit_times
*times
;
814 struct boot_times
*boot
;
816 if (strv_extend(units
, name
))
819 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
823 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
825 r
= acquire_boot_times(bus
, &boot
);
829 STRV_FOREACH(c
, deps
) {
830 times
= hashmap_get(unit_times_hashmap
, *c
);
833 && times
->activated
<= boot
->finish_time
834 && (times
->activated
>= service_longest
835 || service_longest
== 0)) {
836 service_longest
= times
->activated
;
841 if (service_longest
== 0 )
844 STRV_FOREACH(c
, deps
) {
845 times
= hashmap_get(unit_times_hashmap
, *c
);
846 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
853 STRV_FOREACH(c
, deps
) {
854 times
= hashmap_get(unit_times_hashmap
, *c
);
857 || times
->activated
> boot
->finish_time
858 || service_longest
- times
->activated
> arg_fuzz
)
863 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
867 if (strv_contains(*units
, *c
)) {
868 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
875 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
876 (branches
<< 1) | (to_print
? 1 : 0));
886 static int list_dependencies(sd_bus
*bus
, const char *name
) {
887 _cleanup_strv_free_
char **units
= NULL
;
888 char ts
[FORMAT_TIMESPAN_MAX
];
889 struct unit_times
*times
;
892 _cleanup_free_
char *path
= NULL
;
893 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
894 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
895 struct boot_times
*boot
;
899 path
= unit_dbus_path_from_name(name
);
903 r
= sd_bus_get_property(
905 "org.freedesktop.systemd1",
907 "org.freedesktop.systemd1.Unit",
913 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
917 r
= sd_bus_message_read(reply
, "s", &id
);
919 return bus_log_parse_error(r
);
921 times
= hashmap_get(unit_times_hashmap
, id
);
923 r
= acquire_boot_times(bus
, &boot
);
929 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED
, id
,
930 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_NORMAL
);
931 else if (times
->activated
> boot
->userspace_time
)
932 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
937 return list_dependencies_one(bus
, name
, 0, &units
, 0);
940 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
941 struct unit_times
*times
;
946 n
= acquire_time_data(bus
, ×
);
950 h
= hashmap_new(&string_hash_ops
);
954 for (i
= 0; i
< (unsigned)n
; i
++) {
955 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
959 unit_times_hashmap
= h
;
961 pager_open(arg_no_pager
, false);
963 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
964 "The time the unit takes to start is printed after the \"+\" character.\n");
966 if (!strv_isempty(names
)) {
968 STRV_FOREACH(name
, names
)
969 list_dependencies(bus
, *name
);
971 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
974 free_unit_times(times
, (unsigned) n
);
978 static int analyze_blame(sd_bus
*bus
) {
979 struct unit_times
*times
;
983 n
= acquire_time_data(bus
, ×
);
987 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
989 pager_open(arg_no_pager
, false);
991 for (i
= 0; i
< (unsigned) n
; i
++) {
992 char ts
[FORMAT_TIMESPAN_MAX
];
994 if (times
[i
].time
> 0)
995 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
998 free_unit_times(times
, (unsigned) n
);
1002 static int analyze_time(sd_bus
*bus
) {
1003 _cleanup_free_
char *buf
= NULL
;
1006 r
= pretty_boot_time(bus
, &buf
);
1014 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
[]) {
1015 _cleanup_strv_free_
char **units
= NULL
;
1018 bool match_patterns
;
1024 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1026 if (!strv_isempty(from_patterns
) &&
1028 !strv_fnmatch(from_patterns
, u
->id
, 0))
1031 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1035 STRV_FOREACH(unit
, units
) {
1036 bool match_patterns2
;
1038 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1040 if (!strv_isempty(to_patterns
) &&
1042 !strv_fnmatch(to_patterns
, *unit
, 0))
1045 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1048 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1054 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1060 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1061 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1066 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1067 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1070 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1073 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1076 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1084 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1085 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1089 STRV_FOREACH(pattern
, patterns
) {
1090 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1091 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1093 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1096 if (string_is_glob(*pattern
))
1099 unit
= unit_dbus_path_from_name(*pattern
);
1103 r
= sd_bus_get_property_string(
1105 "org.freedesktop.systemd1",
1107 "org.freedesktop.systemd1.Unit",
1112 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1114 if (!streq(*pattern
, unit_id
)) {
1115 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1120 *ret
= expanded_patterns
;
1121 expanded_patterns
= NULL
; /* do not free */
1126 static int dot(sd_bus
*bus
, char* patterns
[]) {
1127 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1128 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1129 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1130 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1131 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1135 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1139 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1143 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1147 r
= sd_bus_call_method(
1149 "org.freedesktop.systemd1",
1150 "/org/freedesktop/systemd1",
1151 "org.freedesktop.systemd1.Manager",
1157 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1161 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1163 return bus_log_parse_error(r
);
1165 printf("digraph systemd {\n");
1167 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1169 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1174 return bus_log_parse_error(r
);
1178 log_info(" Color legend: black = Requires\n"
1179 " dark blue = Requisite\n"
1180 " dark grey = Wants\n"
1181 " red = Conflicts\n"
1182 " green = After\n");
1185 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1186 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1191 static int dump(sd_bus
*bus
, char **args
) {
1192 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1193 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1194 const char *text
= NULL
;
1197 if (!strv_isempty(args
)) {
1198 log_error("Too many arguments.");
1202 pager_open(arg_no_pager
, false);
1204 r
= sd_bus_call_method(
1206 "org.freedesktop.systemd1",
1207 "/org/freedesktop/systemd1",
1208 "org.freedesktop.systemd1.Manager",
1214 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1216 r
= sd_bus_message_read(reply
, "s", &text
);
1218 return bus_log_parse_error(r
);
1220 fputs(text
, stdout
);
1224 static int set_log_level(sd_bus
*bus
, char **args
) {
1225 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1231 if (strv_length(args
) != 1) {
1232 log_error("This command expects one argument only.");
1236 r
= sd_bus_set_property(
1238 "org.freedesktop.systemd1",
1239 "/org/freedesktop/systemd1",
1240 "org.freedesktop.systemd1.Manager",
1246 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1251 static int set_log_target(sd_bus
*bus
, char **args
) {
1252 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1258 if (strv_length(args
) != 1) {
1259 log_error("This command expects one argument only.");
1263 r
= sd_bus_set_property(
1265 "org.freedesktop.systemd1",
1266 "/org/freedesktop/systemd1",
1267 "org.freedesktop.systemd1.Manager",
1273 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1278 static void help(void) {
1280 pager_open(arg_no_pager
, false);
1282 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1283 "Profile systemd, show unit dependencies, check unit files.\n\n"
1284 " -h --help Show this help\n"
1285 " --version Show package version\n"
1286 " --no-pager Do not pipe output into a pager\n"
1287 " --system Operate on system systemd instance\n"
1288 " --user Operate on user systemd instance\n"
1289 " -H --host=[USER@]HOST Operate on remote host\n"
1290 " -M --machine=CONTAINER Operate on local container\n"
1291 " --order Show only order in the graph\n"
1292 " --require Show only requirement in the graph\n"
1293 " --from-pattern=GLOB Show only origins in the graph\n"
1294 " --to-pattern=GLOB Show only destinations in the graph\n"
1295 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1296 " earlier than the latest in the branch\n"
1297 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1299 " time Print time spent in the kernel\n"
1300 " blame Print list of running units ordered by time to init\n"
1301 " critical-chain Print a tree of the time critical chain of units\n"
1302 " plot Output SVG graphic showing service initialization\n"
1303 " dot Output dependency graph in dot(1) format\n"
1304 " set-log-level LEVEL Set logging threshold for manager\n"
1305 " set-log-target TARGET Set logging target for manager\n"
1306 " dump Output state serialization of service manager\n"
1307 " verify FILE... Check unit files for correctness\n"
1308 , program_invocation_short_name
);
1310 /* When updating this list, including descriptions, apply
1311 * changes to shell-completion/bash/systemd-analyze and
1312 * shell-completion/zsh/_systemd-analyze too. */
1315 static int parse_argv(int argc
, char *argv
[]) {
1317 ARG_VERSION
= 0x100,
1322 ARG_DOT_FROM_PATTERN
,
1329 static const struct option options
[] = {
1330 { "help", no_argument
, NULL
, 'h' },
1331 { "version", no_argument
, NULL
, ARG_VERSION
},
1332 { "order", no_argument
, NULL
, ARG_ORDER
},
1333 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1334 { "user", no_argument
, NULL
, ARG_USER
},
1335 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1336 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1337 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1338 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1339 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1340 { "man", optional_argument
, NULL
, ARG_MAN
},
1341 { "host", required_argument
, NULL
, 'H' },
1342 { "machine", required_argument
, NULL
, 'M' },
1351 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1370 arg_dot
= DEP_ORDER
;
1374 arg_dot
= DEP_REQUIRE
;
1377 case ARG_DOT_FROM_PATTERN
:
1378 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1383 case ARG_DOT_TO_PATTERN
:
1384 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1390 r
= parse_sec(optarg
, &arg_fuzz
);
1396 arg_no_pager
= true;
1400 arg_transport
= BUS_TRANSPORT_REMOTE
;
1405 arg_transport
= BUS_TRANSPORT_MACHINE
;
1411 r
= parse_boolean(optarg
);
1413 log_error("Failed to parse --man= argument.");
1427 assert_not_reached("Unhandled option code.");
1430 return 1; /* work to do */
1433 int main(int argc
, char *argv
[]) {
1436 setlocale(LC_ALL
, "");
1437 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1438 log_parse_environment();
1441 r
= parse_argv(argc
, argv
);
1445 if (streq_ptr(argv
[optind
], "verify"))
1446 r
= verify_units(argv
+optind
+1,
1447 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1450 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1452 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1454 log_error_errno(r
, "Failed to create bus connection: %m");
1458 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1459 r
= analyze_time(bus
);
1460 else if (streq(argv
[optind
], "blame"))
1461 r
= analyze_blame(bus
);
1462 else if (streq(argv
[optind
], "critical-chain"))
1463 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1464 else if (streq(argv
[optind
], "plot"))
1465 r
= analyze_plot(bus
);
1466 else if (streq(argv
[optind
], "dot"))
1467 r
= dot(bus
, argv
+optind
+1);
1468 else if (streq(argv
[optind
], "dump"))
1469 r
= dump(bus
, argv
+optind
+1);
1470 else if (streq(argv
[optind
], "set-log-level"))
1471 r
= set_log_level(bus
, argv
+optind
+1);
1472 else if (streq(argv
[optind
], "set-log-target"))
1473 r
= set_log_target(bus
, argv
+optind
+1);
1475 log_error("Unknown operation '%s'.", argv
[optind
]);
1481 strv_free(arg_dot_from_patterns
);
1482 strv_free(arg_dot_to_patterns
);
1484 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;