2 This file is part of systemd.
4 Copyright 2010-2013 Lennart Poettering
5 Copyright 2013 Simon Peeters
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
28 #include "alloc-util.h"
29 #include "analyze-verify.h"
30 #include "bus-error.h"
31 #include "bus-unit-util.h"
33 #include "glob-util.h"
35 #include "locale-util.h"
38 #include "parse-util.h"
40 #include "seccomp-util.h"
45 #include "terminal-util.h"
46 #include "unit-name.h"
49 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
50 #define SCALE_Y (20.0)
52 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
54 #define svg(...) printf(__VA_ARGS__)
56 #define svg_bar(class, x1, x2, y) \
57 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
59 SCALE_X * (x1), SCALE_Y * (y), \
60 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
62 #define svg_text(b, x, y, format, ...) \
64 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
65 svg(format, ## __VA_ARGS__); \
74 static char** arg_dot_from_patterns
= NULL
;
75 static char** arg_dot_to_patterns
= NULL
;
76 static usec_t arg_fuzz
= 0;
77 static bool arg_no_pager
= false;
78 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
79 static char *arg_host
= NULL
;
80 static bool arg_user
= false;
81 static bool arg_man
= true;
82 static bool arg_generators
= false;
88 usec_t kernel_done_time
;
90 usec_t userspace_time
;
92 usec_t security_start_time
;
93 usec_t security_finish_time
;
94 usec_t generators_start_time
;
95 usec_t generators_finish_time
;
96 usec_t unitsload_start_time
;
97 usec_t unitsload_finish_time
;
100 * If we're analyzing the user instance, all timestamps will be offset
101 * by its own start-up timestamp, which may be arbitrarily big.
102 * With "plot", this causes arbitrarily wide output SVG files which almost
103 * completely consist of empty space. Thus we cancel out this offset.
105 * This offset is subtracted from times above by acquire_boot_times(),
106 * but it still needs to be subtracted from unit-specific timestamps
107 * (so it is stored here for reference).
109 usec_t reverse_offset
;
124 char *kernel_release
;
125 char *kernel_version
;
126 char *os_pretty_name
;
127 char *virtualization
;
131 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
132 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
141 r
= sd_bus_get_property_trivial(
143 "org.freedesktop.systemd1",
151 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
158 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
159 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
167 r
= sd_bus_get_property_strv(
169 "org.freedesktop.systemd1",
171 "org.freedesktop.systemd1.Unit",
176 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
183 static int compare_unit_time(const void *a
, const void *b
) {
184 return compare(((struct unit_times
*)b
)->time
,
185 ((struct unit_times
*)a
)->time
);
188 static int compare_unit_start(const void *a
, const void *b
) {
189 return compare(((struct unit_times
*)a
)->activating
,
190 ((struct unit_times
*)b
)->activating
);
193 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
194 struct unit_times
*p
;
196 for (p
= t
; p
< t
+ n
; p
++)
202 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
211 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
212 static struct boot_times times
;
213 static bool cached
= false;
218 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
220 if (bus_get_uint64_property(bus
,
221 "/org/freedesktop/systemd1",
222 "org.freedesktop.systemd1.Manager",
223 "FirmwareTimestampMonotonic",
224 ×
.firmware_time
) < 0 ||
225 bus_get_uint64_property(bus
,
226 "/org/freedesktop/systemd1",
227 "org.freedesktop.systemd1.Manager",
228 "LoaderTimestampMonotonic",
229 ×
.loader_time
) < 0 ||
230 bus_get_uint64_property(bus
,
231 "/org/freedesktop/systemd1",
232 "org.freedesktop.systemd1.Manager",
234 ×
.kernel_time
) < 0 ||
235 bus_get_uint64_property(bus
,
236 "/org/freedesktop/systemd1",
237 "org.freedesktop.systemd1.Manager",
238 "InitRDTimestampMonotonic",
239 ×
.initrd_time
) < 0 ||
240 bus_get_uint64_property(bus
,
241 "/org/freedesktop/systemd1",
242 "org.freedesktop.systemd1.Manager",
243 "UserspaceTimestampMonotonic",
244 ×
.userspace_time
) < 0 ||
245 bus_get_uint64_property(bus
,
246 "/org/freedesktop/systemd1",
247 "org.freedesktop.systemd1.Manager",
248 "FinishTimestampMonotonic",
249 ×
.finish_time
) < 0 ||
250 bus_get_uint64_property(bus
,
251 "/org/freedesktop/systemd1",
252 "org.freedesktop.systemd1.Manager",
253 "SecurityStartTimestampMonotonic",
254 ×
.security_start_time
) < 0 ||
255 bus_get_uint64_property(bus
,
256 "/org/freedesktop/systemd1",
257 "org.freedesktop.systemd1.Manager",
258 "SecurityFinishTimestampMonotonic",
259 ×
.security_finish_time
) < 0 ||
260 bus_get_uint64_property(bus
,
261 "/org/freedesktop/systemd1",
262 "org.freedesktop.systemd1.Manager",
263 "GeneratorsStartTimestampMonotonic",
264 ×
.generators_start_time
) < 0 ||
265 bus_get_uint64_property(bus
,
266 "/org/freedesktop/systemd1",
267 "org.freedesktop.systemd1.Manager",
268 "GeneratorsFinishTimestampMonotonic",
269 ×
.generators_finish_time
) < 0 ||
270 bus_get_uint64_property(bus
,
271 "/org/freedesktop/systemd1",
272 "org.freedesktop.systemd1.Manager",
273 "UnitsLoadStartTimestampMonotonic",
274 ×
.unitsload_start_time
) < 0 ||
275 bus_get_uint64_property(bus
,
276 "/org/freedesktop/systemd1",
277 "org.freedesktop.systemd1.Manager",
278 "UnitsLoadFinishTimestampMonotonic",
279 ×
.unitsload_finish_time
) < 0)
282 if (times
.finish_time
<= 0) {
283 log_error("Bootup is not yet finished. Please try again later.");
289 * User-instance-specific timestamps processing
290 * (see comment to reverse_offset in struct boot_times).
292 times
.reverse_offset
= times
.userspace_time
;
294 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
295 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
297 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
298 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
300 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
301 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
303 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
304 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
306 if (times
.initrd_time
)
307 times
.kernel_done_time
= times
.initrd_time
;
309 times
.kernel_done_time
= times
.userspace_time
;
319 static void free_host_info(struct host_info
*hi
) {
325 free(hi
->kernel_name
);
326 free(hi
->kernel_release
);
327 free(hi
->kernel_version
);
328 free(hi
->os_pretty_name
);
329 free(hi
->virtualization
);
330 free(hi
->architecture
);
334 DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info
*, free_host_info
);
336 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
337 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
338 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
340 struct boot_times
*boot_times
= NULL
;
341 struct unit_times
*unit_times
= NULL
;
345 r
= acquire_boot_times(bus
, &boot_times
);
349 r
= sd_bus_call_method(
351 "org.freedesktop.systemd1",
352 "/org/freedesktop/systemd1",
353 "org.freedesktop.systemd1.Manager",
358 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
362 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
364 bus_log_parse_error(r
);
368 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
369 struct unit_times
*t
;
371 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
379 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
381 if (bus_get_uint64_property(bus
, u
.unit_path
,
382 "org.freedesktop.systemd1.Unit",
383 "InactiveExitTimestampMonotonic",
384 &t
->activating
) < 0 ||
385 bus_get_uint64_property(bus
, u
.unit_path
,
386 "org.freedesktop.systemd1.Unit",
387 "ActiveEnterTimestampMonotonic",
388 &t
->activated
) < 0 ||
389 bus_get_uint64_property(bus
, u
.unit_path
,
390 "org.freedesktop.systemd1.Unit",
391 "ActiveExitTimestampMonotonic",
392 &t
->deactivating
) < 0 ||
393 bus_get_uint64_property(bus
, u
.unit_path
,
394 "org.freedesktop.systemd1.Unit",
395 "InactiveEnterTimestampMonotonic",
396 &t
->deactivated
) < 0) {
401 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
402 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
403 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
404 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
406 if (t
->activated
>= t
->activating
)
407 t
->time
= t
->activated
- t
->activating
;
408 else if (t
->deactivated
>= t
->activating
)
409 t
->time
= t
->deactivated
- t
->activating
;
413 if (t
->activating
== 0)
416 t
->name
= strdup(u
.id
);
417 if (t
->name
== NULL
) {
424 bus_log_parse_error(r
);
433 free_unit_times(unit_times
, (unsigned) c
);
437 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
438 static const struct bus_properties_map hostname_map
[] = {
439 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
440 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
441 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
442 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
443 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
447 static const struct bus_properties_map manager_map
[] = {
448 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
449 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
453 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
454 _cleanup_(free_host_infop
) struct host_info
*host
;
457 host
= new0(struct host_info
, 1);
461 r
= bus_map_all_properties(bus
,
462 "org.freedesktop.hostname1",
463 "/org/freedesktop/hostname1",
468 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error
, r
));
470 r
= bus_map_all_properties(bus
,
471 "org.freedesktop.systemd1",
472 "/org/freedesktop/systemd1",
477 return log_error_errno(r
, "Failed to get host information from systemd: %s", bus_error_message(&error
, r
));
485 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
486 char ts
[FORMAT_TIMESPAN_MAX
];
487 struct boot_times
*t
;
488 static char buf
[4096];
493 r
= acquire_boot_times(bus
, &t
);
500 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
501 if (t
->firmware_time
)
502 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
504 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
506 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
507 if (t
->initrd_time
> 0)
508 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
510 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
511 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
521 static void svg_graph_box(double height
, double begin
, double end
) {
524 /* outside box, fill */
525 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
526 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
528 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
529 /* lines for each second */
530 if (i
% 5000000 == 0)
531 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
532 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
533 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
534 else if (i
% 1000000 == 0)
535 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
536 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
537 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
539 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
540 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
544 static int analyze_plot(sd_bus
*bus
) {
545 _cleanup_(free_host_infop
) struct host_info
*host
= NULL
;
546 struct unit_times
*times
;
547 struct boot_times
*boot
;
550 _cleanup_free_
char *pretty_times
= NULL
;
551 struct unit_times
*u
;
553 n
= acquire_boot_times(bus
, &boot
);
557 n
= pretty_boot_time(bus
, &pretty_times
);
561 n
= acquire_host_info(bus
, &host
);
565 n
= acquire_time_data(bus
, ×
);
569 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
571 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
575 if (boot
->firmware_time
> boot
->loader_time
)
577 if (boot
->loader_time
) {
582 if (boot
->initrd_time
)
584 if (boot
->kernel_time
)
587 for (u
= times
; u
< times
+ n
; u
++) {
588 double text_start
, text_width
;
590 if (u
->activating
< boot
->userspace_time
||
591 u
->activating
> boot
->finish_time
) {
592 u
->name
= mfree(u
->name
);
596 /* If the text cannot fit on the left side then
597 * increase the svg width so it fits on the right.
598 * TODO: calculate the text width more accurately */
599 text_width
= 8.0 * strlen(u
->name
);
600 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
601 if (text_width
> text_start
&& text_width
+ text_start
> width
)
602 width
= text_width
+ text_start
;
604 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
605 && u
->activated
== 0 && u
->deactivating
== 0)
606 u
->activated
= u
->deactivating
= u
->deactivated
;
607 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
608 u
->activated
= boot
->finish_time
;
609 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
610 u
->deactivating
= boot
->finish_time
;
611 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
612 u
->deactivated
= boot
->finish_time
;
616 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
617 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
618 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
620 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
621 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
622 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
623 5 * SCALE_Y
/* legend */);
625 /* write some basic info as a comment, including some help */
626 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
627 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
628 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
629 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
630 "<!-- point your browser to this file. -->\n\n"
631 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", PACKAGE_VERSION
);
634 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
635 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
636 " rect.background { fill: rgb(255,255,255); }\n"
637 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
638 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
639 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
640 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
641 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
642 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
643 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
644 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
645 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
646 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
647 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
648 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
649 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
651 " line.sec5 { stroke-width: 2; }\n"
652 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
653 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
654 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
655 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
656 " text.sec { font-size: 10px; }\n"
657 " ]]>\n </style>\n</defs>\n\n");
659 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
660 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
661 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
662 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
663 strempty(host
->hostname
),
664 strempty(host
->kernel_name
),
665 strempty(host
->kernel_release
),
666 strempty(host
->kernel_version
),
667 strempty(host
->architecture
),
668 strempty(host
->virtualization
));
670 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
671 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
673 if (boot
->firmware_time
) {
674 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
675 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
678 if (boot
->loader_time
) {
679 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
680 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
683 if (boot
->kernel_time
) {
684 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
685 svg_text(true, 0, y
, "kernel");
688 if (boot
->initrd_time
) {
689 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
690 svg_text(true, boot
->initrd_time
, y
, "initrd");
693 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
694 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
695 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
696 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
697 svg_text(true, boot
->userspace_time
, y
, "systemd");
700 for (u
= times
; u
< times
+ n
; u
++) {
701 char ts
[FORMAT_TIMESPAN_MAX
];
707 svg_bar("activating", u
->activating
, u
->activated
, y
);
708 svg_bar("active", u
->activated
, u
->deactivating
, y
);
709 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
711 /* place the text on the left if we have passed the half of the svg width */
712 b
= u
->activating
* SCALE_X
< width
/ 2;
714 svg_text(b
, u
->activating
, y
, "%s (%s)",
715 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
717 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
724 svg("<g transform=\"translate(20,100)\">\n");
726 svg_bar("activating", 0, 300000, y
);
727 svg_text(true, 400000, y
, "Activating");
729 svg_bar("active", 0, 300000, y
);
730 svg_text(true, 400000, y
, "Active");
732 svg_bar("deactivating", 0, 300000, y
);
733 svg_text(true, 400000, y
, "Deactivating");
735 svg_bar("security", 0, 300000, y
);
736 svg_text(true, 400000, y
, "Setting up security module");
738 svg_bar("generators", 0, 300000, y
);
739 svg_text(true, 400000, y
, "Generators");
741 svg_bar("unitsload", 0, 300000, y
);
742 svg_text(true, 400000, y
, "Loading unit files");
749 free_unit_times(times
, (unsigned) n
);
755 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
756 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
758 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
760 for (i
= level
; i
!= 0; i
--)
761 printf("%s", special_glyph(branches
& (1 << (i
-1)) ? TREE_VERTICAL
: TREE_SPACE
));
763 printf("%s", special_glyph(last
? TREE_RIGHT
: TREE_BRANCH
));
767 printf("%s%s @%s +%s%s", ansi_highlight_red(), name
,
768 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
769 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ansi_normal());
770 else if (times
->activated
> boot
->userspace_time
)
771 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
781 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
782 _cleanup_free_
char *path
= NULL
;
788 path
= unit_dbus_path_from_name(name
);
792 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
795 static Hashmap
*unit_times_hashmap
;
797 static int list_dependencies_compare(const void *_a
, const void *_b
) {
798 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
799 usec_t usa
= 0, usb
= 0;
800 struct unit_times
*times
;
802 times
= hashmap_get(unit_times_hashmap
, *a
);
804 usa
= times
->activated
;
805 times
= hashmap_get(unit_times_hashmap
, *b
);
807 usb
= times
->activated
;
812 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
813 unsigned int branches
) {
814 _cleanup_strv_free_
char **deps
= NULL
;
817 usec_t service_longest
= 0;
819 struct unit_times
*times
;
820 struct boot_times
*boot
;
822 if (strv_extend(units
, name
))
825 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
829 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
831 r
= acquire_boot_times(bus
, &boot
);
835 STRV_FOREACH(c
, deps
) {
836 times
= hashmap_get(unit_times_hashmap
, *c
);
839 && times
->activated
<= boot
->finish_time
840 && (times
->activated
>= service_longest
841 || service_longest
== 0)) {
842 service_longest
= times
->activated
;
847 if (service_longest
== 0 )
850 STRV_FOREACH(c
, deps
) {
851 times
= hashmap_get(unit_times_hashmap
, *c
);
852 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
859 STRV_FOREACH(c
, deps
) {
860 times
= hashmap_get(unit_times_hashmap
, *c
);
863 || times
->activated
> boot
->finish_time
864 || service_longest
- times
->activated
> arg_fuzz
)
869 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
873 if (strv_contains(*units
, *c
)) {
874 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
881 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
882 (branches
<< 1) | (to_print
? 1 : 0));
892 static int list_dependencies(sd_bus
*bus
, const char *name
) {
893 _cleanup_strv_free_
char **units
= NULL
;
894 char ts
[FORMAT_TIMESPAN_MAX
];
895 struct unit_times
*times
;
898 _cleanup_free_
char *path
= NULL
;
899 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
900 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
901 struct boot_times
*boot
;
905 path
= unit_dbus_path_from_name(name
);
909 r
= sd_bus_get_property(
911 "org.freedesktop.systemd1",
913 "org.freedesktop.systemd1.Unit",
919 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
923 r
= sd_bus_message_read(reply
, "s", &id
);
925 return bus_log_parse_error(r
);
927 times
= hashmap_get(unit_times_hashmap
, id
);
929 r
= acquire_boot_times(bus
, &boot
);
935 printf("%s%s +%s%s\n", ansi_highlight_red(), id
,
936 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ansi_normal());
937 else if (times
->activated
> boot
->userspace_time
)
938 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
943 return list_dependencies_one(bus
, name
, 0, &units
, 0);
946 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
947 struct unit_times
*times
;
952 n
= acquire_time_data(bus
, ×
);
956 h
= hashmap_new(&string_hash_ops
);
960 for (i
= 0; i
< (unsigned)n
; i
++) {
961 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
965 unit_times_hashmap
= h
;
967 pager_open(arg_no_pager
, false);
969 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
970 "The time the unit takes to start is printed after the \"+\" character.\n");
972 if (!strv_isempty(names
)) {
974 STRV_FOREACH(name
, names
)
975 list_dependencies(bus
, *name
);
977 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
980 free_unit_times(times
, (unsigned) n
);
984 static int analyze_blame(sd_bus
*bus
) {
985 struct unit_times
*times
;
989 n
= acquire_time_data(bus
, ×
);
993 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
995 pager_open(arg_no_pager
, false);
997 for (i
= 0; i
< (unsigned) n
; i
++) {
998 char ts
[FORMAT_TIMESPAN_MAX
];
1000 if (times
[i
].time
> 0)
1001 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
1004 free_unit_times(times
, (unsigned) n
);
1008 static int analyze_time(sd_bus
*bus
) {
1009 _cleanup_free_
char *buf
= NULL
;
1012 r
= pretty_boot_time(bus
, &buf
);
1020 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
[]) {
1021 _cleanup_strv_free_
char **units
= NULL
;
1024 bool match_patterns
;
1030 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1032 if (!strv_isempty(from_patterns
) &&
1034 !strv_fnmatch(from_patterns
, u
->id
, 0))
1037 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1041 STRV_FOREACH(unit
, units
) {
1042 bool match_patterns2
;
1044 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1046 if (!strv_isempty(to_patterns
) &&
1048 !strv_fnmatch(to_patterns
, *unit
, 0))
1051 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1054 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1060 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1066 if (IN_SET(arg_dot
, DEP_ORDER
, DEP_ALL
)) {
1067 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1072 if (IN_SET(arg_dot
, DEP_REQUIRE
, DEP_ALL
)) {
1073 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1076 r
= graph_one_property(bus
, u
, "Requisite", "darkblue", patterns
, from_patterns
, to_patterns
);
1079 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1082 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1090 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1091 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1095 STRV_FOREACH(pattern
, patterns
) {
1096 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1097 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1099 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1102 if (string_is_glob(*pattern
))
1105 unit
= unit_dbus_path_from_name(*pattern
);
1109 r
= sd_bus_get_property_string(
1111 "org.freedesktop.systemd1",
1113 "org.freedesktop.systemd1.Unit",
1118 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1120 if (!streq(*pattern
, unit_id
)) {
1121 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1126 *ret
= expanded_patterns
;
1127 expanded_patterns
= NULL
; /* do not free */
1132 static int dot(sd_bus
*bus
, char* patterns
[]) {
1133 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1134 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1135 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1136 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1137 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1141 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1145 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1149 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1153 r
= sd_bus_call_method(
1155 "org.freedesktop.systemd1",
1156 "/org/freedesktop/systemd1",
1157 "org.freedesktop.systemd1.Manager",
1163 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1167 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1169 return bus_log_parse_error(r
);
1171 printf("digraph systemd {\n");
1173 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1175 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1180 return bus_log_parse_error(r
);
1184 log_info(" Color legend: black = Requires\n"
1185 " dark blue = Requisite\n"
1186 " dark grey = Wants\n"
1187 " red = Conflicts\n"
1188 " green = After\n");
1191 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1192 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1197 static int dump(sd_bus
*bus
, char **args
) {
1198 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
1199 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1200 const char *text
= NULL
;
1203 if (!strv_isempty(args
)) {
1204 log_error("Too many arguments.");
1208 pager_open(arg_no_pager
, false);
1210 r
= sd_bus_call_method(
1212 "org.freedesktop.systemd1",
1213 "/org/freedesktop/systemd1",
1214 "org.freedesktop.systemd1.Manager",
1220 return log_error_errno(r
, "Failed issue method call: %s", bus_error_message(&error
, r
));
1222 r
= sd_bus_message_read(reply
, "s", &text
);
1224 return bus_log_parse_error(r
);
1226 fputs(text
, stdout
);
1230 static int set_log_level(sd_bus
*bus
, char **args
) {
1231 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1237 if (strv_length(args
) != 1) {
1238 log_error("This command expects one argument only.");
1242 r
= sd_bus_set_property(
1244 "org.freedesktop.systemd1",
1245 "/org/freedesktop/systemd1",
1246 "org.freedesktop.systemd1.Manager",
1252 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1257 static int get_log_level(sd_bus
*bus
, char **args
) {
1258 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1260 _cleanup_free_
char *level
= NULL
;
1265 if (!strv_isempty(args
)) {
1266 log_error("Too many arguments.");
1270 r
= sd_bus_get_property_string(
1272 "org.freedesktop.systemd1",
1273 "/org/freedesktop/systemd1",
1274 "org.freedesktop.systemd1.Manager",
1279 return log_error_errno(r
, "Failed to get log level: %s", bus_error_message(&error
, r
));
1285 static int set_log_target(sd_bus
*bus
, char **args
) {
1286 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1292 if (strv_length(args
) != 1) {
1293 log_error("This command expects one argument only.");
1297 r
= sd_bus_set_property(
1299 "org.freedesktop.systemd1",
1300 "/org/freedesktop/systemd1",
1301 "org.freedesktop.systemd1.Manager",
1307 return log_error_errno(r
, "Failed to issue method call: %s", bus_error_message(&error
, r
));
1312 static int get_log_target(sd_bus
*bus
, char **args
) {
1313 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
1315 _cleanup_free_
char *target
= NULL
;
1320 if (!strv_isempty(args
)) {
1321 log_error("Too many arguments.");
1325 r
= sd_bus_get_property_string(
1327 "org.freedesktop.systemd1",
1328 "/org/freedesktop/systemd1",
1329 "org.freedesktop.systemd1.Manager",
1334 return log_error_errno(r
, "Failed to get log target: %s", bus_error_message(&error
, r
));
1341 static void dump_syscall_filter(const SyscallFilterSet
*set
) {
1342 const char *syscall
;
1344 printf("%s\n", set
->name
);
1345 printf(" # %s\n", set
->help
);
1346 NULSTR_FOREACH(syscall
, set
->value
)
1347 printf(" %s\n", syscall
);
1350 static int dump_syscall_filters(char** names
) {
1353 pager_open(arg_no_pager
, false);
1355 if (strv_isempty(names
)) {
1358 for (i
= 0; i
< _SYSCALL_FILTER_SET_MAX
; i
++) {
1361 dump_syscall_filter(syscall_filter_sets
+ i
);
1367 STRV_FOREACH(name
, names
) {
1368 const SyscallFilterSet
*set
;
1373 set
= syscall_filter_set_find(*name
);
1375 /* make sure the error appears below normal output */
1378 log_error("Filter set \"%s\" not found.", *name
);
1382 dump_syscall_filter(set
);
1391 static int dump_syscall_filters(char** names
) {
1392 log_error("Not compiled with syscall filters, sorry.");
1397 static void help(void) {
1399 pager_open(arg_no_pager
, false);
1401 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1402 "Profile systemd, show unit dependencies, check unit files.\n\n"
1403 " -h --help Show this help\n"
1404 " --version Show package version\n"
1405 " --no-pager Do not pipe output into a pager\n"
1406 " --system Operate on system systemd instance\n"
1407 " --user Operate on user systemd instance\n"
1408 " -H --host=[USER@]HOST Operate on remote host\n"
1409 " -M --machine=CONTAINER Operate on local container\n"
1410 " --order Show only order in the graph\n"
1411 " --require Show only requirement in the graph\n"
1412 " --from-pattern=GLOB Show only origins in the graph\n"
1413 " --to-pattern=GLOB Show only destinations in the graph\n"
1414 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1415 " earlier than the latest in the branch\n"
1416 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1417 " --generators[=BOOL] Do [not] run unit generators (requires privileges)\n\n"
1419 " time Print time spent in the kernel\n"
1420 " blame Print list of running units ordered by time to init\n"
1421 " critical-chain Print a tree of the time critical chain of units\n"
1422 " plot Output SVG graphic showing service initialization\n"
1423 " dot Output dependency graph in man:dot(1) format\n"
1424 " set-log-level LEVEL Set logging threshold for manager\n"
1425 " set-log-target TARGET Set logging target for manager\n"
1426 " get-log-level Get logging threshold for manager\n"
1427 " get-log-target Get logging target for manager\n"
1428 " dump Output state serialization of service manager\n"
1429 " syscall-filter [NAME...] Print list of syscalls in seccomp filter\n"
1430 " verify FILE... Check unit files for correctness\n"
1431 , program_invocation_short_name
);
1433 /* When updating this list, including descriptions, apply
1434 * changes to shell-completion/bash/systemd-analyze and
1435 * shell-completion/zsh/_systemd-analyze too. */
1438 static int parse_argv(int argc
, char *argv
[]) {
1440 ARG_VERSION
= 0x100,
1445 ARG_DOT_FROM_PATTERN
,
1453 static const struct option options
[] = {
1454 { "help", no_argument
, NULL
, 'h' },
1455 { "version", no_argument
, NULL
, ARG_VERSION
},
1456 { "order", no_argument
, NULL
, ARG_ORDER
},
1457 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1458 { "user", no_argument
, NULL
, ARG_USER
},
1459 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1460 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1461 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1462 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1463 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1464 { "man", optional_argument
, NULL
, ARG_MAN
},
1465 { "generators", optional_argument
, NULL
, ARG_GENERATORS
},
1466 { "host", required_argument
, NULL
, 'H' },
1467 { "machine", required_argument
, NULL
, 'M' },
1476 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1495 arg_dot
= DEP_ORDER
;
1499 arg_dot
= DEP_REQUIRE
;
1502 case ARG_DOT_FROM_PATTERN
:
1503 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1508 case ARG_DOT_TO_PATTERN
:
1509 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1515 r
= parse_sec(optarg
, &arg_fuzz
);
1521 arg_no_pager
= true;
1525 arg_transport
= BUS_TRANSPORT_REMOTE
;
1530 arg_transport
= BUS_TRANSPORT_MACHINE
;
1536 r
= parse_boolean(optarg
);
1538 log_error("Failed to parse --man= argument.");
1548 case ARG_GENERATORS
:
1550 r
= parse_boolean(optarg
);
1552 log_error("Failed to parse --generators= argument.");
1556 arg_generators
= !!r
;
1558 arg_generators
= true;
1566 assert_not_reached("Unhandled option code.");
1569 return 1; /* work to do */
1572 int main(int argc
, char *argv
[]) {
1575 setlocale(LC_ALL
, "");
1576 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1577 log_parse_environment();
1580 r
= parse_argv(argc
, argv
);
1584 if (streq_ptr(argv
[optind
], "verify"))
1585 r
= verify_units(argv
+optind
+1,
1586 arg_user
? UNIT_FILE_USER
: UNIT_FILE_SYSTEM
,
1590 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
1592 r
= bus_connect_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1594 log_error_errno(r
, "Failed to create bus connection: %m");
1598 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1599 r
= analyze_time(bus
);
1600 else if (streq(argv
[optind
], "blame"))
1601 r
= analyze_blame(bus
);
1602 else if (streq(argv
[optind
], "critical-chain"))
1603 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1604 else if (streq(argv
[optind
], "plot"))
1605 r
= analyze_plot(bus
);
1606 else if (streq(argv
[optind
], "dot"))
1607 r
= dot(bus
, argv
+optind
+1);
1608 else if (streq(argv
[optind
], "dump"))
1609 r
= dump(bus
, argv
+optind
+1);
1610 else if (streq(argv
[optind
], "set-log-level"))
1611 r
= set_log_level(bus
, argv
+optind
+1);
1612 else if (streq(argv
[optind
], "get-log-level"))
1613 r
= get_log_level(bus
, argv
+optind
+1);
1614 else if (streq(argv
[optind
], "set-log-target"))
1615 r
= set_log_target(bus
, argv
+optind
+1);
1616 else if (streq(argv
[optind
], "get-log-target"))
1617 r
= get_log_target(bus
, argv
+optind
+1);
1618 else if (streq(argv
[optind
], "syscall-filter"))
1619 r
= dump_syscall_filters(argv
+optind
+1);
1621 log_error("Unknown operation '%s'.", argv
[optind
]);
1627 strv_free(arg_dot_from_patterns
);
1628 strv_free(arg_dot_to_patterns
);
1630 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;