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 "bus-error.h"
36 #include "unit-name.h"
40 #include "analyze-verify.h"
41 #include "terminal-util.h"
43 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
44 #define SCALE_Y (20.0)
46 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
48 #define svg(...) printf(__VA_ARGS__)
50 #define svg_bar(class, x1, x2, y) \
51 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
53 SCALE_X * (x1), SCALE_Y * (y), \
54 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
56 #define svg_text(b, x, y, format, ...) \
58 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
59 svg(format, ## __VA_ARGS__); \
68 static char** arg_dot_from_patterns
= NULL
;
69 static char** arg_dot_to_patterns
= NULL
;
70 static usec_t arg_fuzz
= 0;
71 static bool arg_no_pager
= false;
72 static BusTransport arg_transport
= BUS_TRANSPORT_LOCAL
;
73 static char *arg_host
= NULL
;
74 static bool arg_user
= false;
75 static bool arg_man
= true;
81 usec_t kernel_done_time
;
83 usec_t userspace_time
;
85 usec_t security_start_time
;
86 usec_t security_finish_time
;
87 usec_t generators_start_time
;
88 usec_t generators_finish_time
;
89 usec_t unitsload_start_time
;
90 usec_t unitsload_finish_time
;
93 * If we're analyzing the user instance, all timestamps will be offset
94 * by its own start-up timestamp, which may be arbitrarily big.
95 * With "plot", this causes arbitrarily wide output SVG files which almost
96 * completely consist of empty space. Thus we cancel out this offset.
98 * This offset is subtracted from times above by acquire_boot_times(),
99 * but it still needs to be subtracted from unit-specific timestamps
100 * (so it is stored here for reference).
102 usec_t reverse_offset
;
117 char *kernel_release
;
118 char *kernel_version
;
119 char *os_pretty_name
;
120 char *virtualization
;
124 static void pager_open_if_enabled(void) {
132 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
133 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
142 r
= sd_bus_get_property_trivial(
144 "org.freedesktop.systemd1",
152 log_error("Failed to parse reply: %s", bus_error_message(&error
, -r
));
159 static int bus_get_unit_property_strv(sd_bus
*bus
, const char *path
, const char *property
, char ***strv
) {
160 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
168 r
= sd_bus_get_property_strv(
170 "org.freedesktop.systemd1",
172 "org.freedesktop.systemd1.Unit",
177 log_error("Failed to get unit property %s: %s", property
, bus_error_message(&error
, -r
));
184 static int compare_unit_time(const void *a
, const void *b
) {
185 return compare(((struct unit_times
*)b
)->time
,
186 ((struct unit_times
*)a
)->time
);
189 static int compare_unit_start(const void *a
, const void *b
) {
190 return compare(((struct unit_times
*)a
)->activating
,
191 ((struct unit_times
*)b
)->activating
);
194 static void free_unit_times(struct unit_times
*t
, unsigned n
) {
195 struct unit_times
*p
;
197 for (p
= t
; p
< t
+ n
; p
++)
203 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
212 static int acquire_boot_times(sd_bus
*bus
, struct boot_times
**bt
) {
213 static struct boot_times times
;
214 static bool cached
= false;
219 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
221 if (bus_get_uint64_property(bus
,
222 "/org/freedesktop/systemd1",
223 "org.freedesktop.systemd1.Manager",
224 "FirmwareTimestampMonotonic",
225 ×
.firmware_time
) < 0 ||
226 bus_get_uint64_property(bus
,
227 "/org/freedesktop/systemd1",
228 "org.freedesktop.systemd1.Manager",
229 "LoaderTimestampMonotonic",
230 ×
.loader_time
) < 0 ||
231 bus_get_uint64_property(bus
,
232 "/org/freedesktop/systemd1",
233 "org.freedesktop.systemd1.Manager",
235 ×
.kernel_time
) < 0 ||
236 bus_get_uint64_property(bus
,
237 "/org/freedesktop/systemd1",
238 "org.freedesktop.systemd1.Manager",
239 "InitRDTimestampMonotonic",
240 ×
.initrd_time
) < 0 ||
241 bus_get_uint64_property(bus
,
242 "/org/freedesktop/systemd1",
243 "org.freedesktop.systemd1.Manager",
244 "UserspaceTimestampMonotonic",
245 ×
.userspace_time
) < 0 ||
246 bus_get_uint64_property(bus
,
247 "/org/freedesktop/systemd1",
248 "org.freedesktop.systemd1.Manager",
249 "FinishTimestampMonotonic",
250 ×
.finish_time
) < 0 ||
251 bus_get_uint64_property(bus
,
252 "/org/freedesktop/systemd1",
253 "org.freedesktop.systemd1.Manager",
254 "SecurityStartTimestampMonotonic",
255 ×
.security_start_time
) < 0 ||
256 bus_get_uint64_property(bus
,
257 "/org/freedesktop/systemd1",
258 "org.freedesktop.systemd1.Manager",
259 "SecurityFinishTimestampMonotonic",
260 ×
.security_finish_time
) < 0 ||
261 bus_get_uint64_property(bus
,
262 "/org/freedesktop/systemd1",
263 "org.freedesktop.systemd1.Manager",
264 "GeneratorsStartTimestampMonotonic",
265 ×
.generators_start_time
) < 0 ||
266 bus_get_uint64_property(bus
,
267 "/org/freedesktop/systemd1",
268 "org.freedesktop.systemd1.Manager",
269 "GeneratorsFinishTimestampMonotonic",
270 ×
.generators_finish_time
) < 0 ||
271 bus_get_uint64_property(bus
,
272 "/org/freedesktop/systemd1",
273 "org.freedesktop.systemd1.Manager",
274 "UnitsLoadStartTimestampMonotonic",
275 ×
.unitsload_start_time
) < 0 ||
276 bus_get_uint64_property(bus
,
277 "/org/freedesktop/systemd1",
278 "org.freedesktop.systemd1.Manager",
279 "UnitsLoadFinishTimestampMonotonic",
280 ×
.unitsload_finish_time
) < 0)
283 if (times
.finish_time
<= 0) {
284 log_error("Bootup is not yet finished. Please try again later.");
290 * User-instance-specific timestamps processing
291 * (see comment to reverse_offset in struct boot_times).
293 times
.reverse_offset
= times
.userspace_time
;
295 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
= times
.userspace_time
= 0;
296 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
298 subtract_timestamp(×
.security_start_time
, times
.reverse_offset
);
299 subtract_timestamp(×
.security_finish_time
, times
.reverse_offset
);
301 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
302 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
304 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
305 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
307 if (times
.initrd_time
)
308 times
.kernel_done_time
= times
.initrd_time
;
310 times
.kernel_done_time
= times
.userspace_time
;
320 static void free_host_info(struct host_info
*hi
) {
322 free(hi
->kernel_name
);
323 free(hi
->kernel_release
);
324 free(hi
->kernel_version
);
325 free(hi
->os_pretty_name
);
326 free(hi
->virtualization
);
327 free(hi
->architecture
);
331 static int acquire_time_data(sd_bus
*bus
, struct unit_times
**out
) {
332 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
333 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
335 struct boot_times
*boot_times
= NULL
;
336 struct unit_times
*unit_times
= NULL
;
340 r
= acquire_boot_times(bus
, &boot_times
);
344 r
= sd_bus_call_method(
346 "org.freedesktop.systemd1",
347 "/org/freedesktop/systemd1",
348 "org.freedesktop.systemd1.Manager",
353 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
357 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
359 bus_log_parse_error(r
);
363 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
364 struct unit_times
*t
;
366 if (!GREEDY_REALLOC(unit_times
, size
, c
+1)) {
374 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
376 if (bus_get_uint64_property(bus
, u
.unit_path
,
377 "org.freedesktop.systemd1.Unit",
378 "InactiveExitTimestampMonotonic",
379 &t
->activating
) < 0 ||
380 bus_get_uint64_property(bus
, u
.unit_path
,
381 "org.freedesktop.systemd1.Unit",
382 "ActiveEnterTimestampMonotonic",
383 &t
->activated
) < 0 ||
384 bus_get_uint64_property(bus
, u
.unit_path
,
385 "org.freedesktop.systemd1.Unit",
386 "ActiveExitTimestampMonotonic",
387 &t
->deactivating
) < 0 ||
388 bus_get_uint64_property(bus
, u
.unit_path
,
389 "org.freedesktop.systemd1.Unit",
390 "InactiveEnterTimestampMonotonic",
391 &t
->deactivated
) < 0) {
396 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
397 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
398 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
399 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
401 if (t
->activated
>= t
->activating
)
402 t
->time
= t
->activated
- t
->activating
;
403 else if (t
->deactivated
>= t
->activating
)
404 t
->time
= t
->deactivated
- t
->activating
;
408 if (t
->activating
== 0)
411 t
->name
= strdup(u
.id
);
412 if (t
->name
== NULL
) {
419 bus_log_parse_error(r
);
428 free_unit_times(unit_times
, (unsigned) c
);
432 static int acquire_host_info(sd_bus
*bus
, struct host_info
**hi
) {
434 struct host_info
*host
;
436 static const struct bus_properties_map hostname_map
[] = {
437 { "Hostname", "s", NULL
, offsetof(struct host_info
, hostname
) },
438 { "KernelName", "s", NULL
, offsetof(struct host_info
, kernel_name
) },
439 { "KernelRelease", "s", NULL
, offsetof(struct host_info
, kernel_release
) },
440 { "KernelVersion", "s", NULL
, offsetof(struct host_info
, kernel_version
) },
441 { "OperatingSystemPrettyName", "s", NULL
, offsetof(struct host_info
, os_pretty_name
) },
445 static const struct bus_properties_map manager_map
[] = {
446 { "Virtualization", "s", NULL
, offsetof(struct host_info
, virtualization
) },
447 { "Architecture", "s", NULL
, offsetof(struct host_info
, architecture
) },
451 host
= new0(struct host_info
, 1);
455 r
= bus_map_all_properties(bus
,
456 "org.freedesktop.hostname1",
457 "/org/freedesktop/hostname1",
463 r
= bus_map_all_properties(bus
,
464 "org.freedesktop.systemd1",
465 "/org/freedesktop/systemd1",
474 free_host_info(host
);
478 static int pretty_boot_time(sd_bus
*bus
, char **_buf
) {
479 char ts
[FORMAT_TIMESPAN_MAX
];
480 struct boot_times
*t
;
481 static char buf
[4096];
486 r
= acquire_boot_times(bus
, &t
);
493 size
= strpcpyf(&ptr
, size
, "Startup finished in ");
494 if (t
->firmware_time
)
495 size
= strpcpyf(&ptr
, size
, "%s (firmware) + ", format_timespan(ts
, sizeof(ts
), t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
));
497 size
= strpcpyf(&ptr
, size
, "%s (loader) + ", format_timespan(ts
, sizeof(ts
), t
->loader_time
, USEC_PER_MSEC
));
499 size
= strpcpyf(&ptr
, size
, "%s (kernel) + ", format_timespan(ts
, sizeof(ts
), t
->kernel_done_time
, USEC_PER_MSEC
));
500 if (t
->initrd_time
> 0)
501 size
= strpcpyf(&ptr
, size
, "%s (initrd) + ", format_timespan(ts
, sizeof(ts
), t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
));
503 size
= strpcpyf(&ptr
, size
, "%s (userspace) ", format_timespan(ts
, sizeof(ts
), t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
));
504 strpcpyf(&ptr
, size
, "= %s", format_timespan(ts
, sizeof(ts
), t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
));
514 static void svg_graph_box(double height
, double begin
, double end
) {
517 /* outside box, fill */
518 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
519 SCALE_X
* (end
- begin
), SCALE_Y
* height
);
521 for (i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+=100000) {
522 /* lines for each second */
523 if (i
% 5000000 == 0)
524 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
525 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
526 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
527 else if (i
% 1000000 == 0)
528 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
529 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
530 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
, SCALE_X
* i
, -5.0, 0.000001 * i
);
532 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
533 SCALE_X
* i
, SCALE_X
* i
, SCALE_Y
* height
);
537 static int analyze_plot(sd_bus
*bus
) {
538 struct unit_times
*times
;
539 struct boot_times
*boot
;
540 struct host_info
*host
= NULL
;
543 _cleanup_free_
char *pretty_times
= NULL
;
544 struct unit_times
*u
;
546 n
= acquire_boot_times(bus
, &boot
);
550 n
= pretty_boot_time(bus
, &pretty_times
);
554 n
= acquire_host_info(bus
, &host
);
558 n
= acquire_time_data(bus
, ×
);
562 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_start
);
564 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
568 if (boot
->firmware_time
> boot
->loader_time
)
570 if (boot
->loader_time
) {
575 if (boot
->initrd_time
)
577 if (boot
->kernel_time
)
580 for (u
= times
; u
< times
+ n
; u
++) {
581 double text_start
, text_width
;
583 if (u
->activating
< boot
->userspace_time
||
584 u
->activating
> boot
->finish_time
) {
585 u
->name
= mfree(u
->name
);
589 /* If the text cannot fit on the left side then
590 * increase the svg width so it fits on the right.
591 * TODO: calculate the text width more accurately */
592 text_width
= 8.0 * strlen(u
->name
);
593 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
594 if (text_width
> text_start
&& text_width
+ text_start
> width
)
595 width
= text_width
+ text_start
;
597 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
598 && u
->activated
== 0 && u
->deactivating
== 0)
599 u
->activated
= u
->deactivating
= u
->deactivated
;
600 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
601 u
->activated
= boot
->finish_time
;
602 if (u
->deactivating
< u
->activated
|| u
->activated
> boot
->finish_time
)
603 u
->deactivating
= boot
->finish_time
;
604 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
605 u
->deactivated
= boot
->finish_time
;
609 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
610 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
611 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
613 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
614 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
615 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
616 5 * SCALE_Y
/* legend */);
618 /* write some basic info as a comment, including some help */
619 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
620 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
621 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
622 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
623 "<!-- point your browser to this file. -->\n\n"
624 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION
);
627 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
628 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
629 " rect.background { fill: rgb(255,255,255); }\n"
630 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
631 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
632 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
633 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
634 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
635 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
636 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
637 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
638 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
639 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
640 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
641 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
642 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
644 " line.sec5 { stroke-width: 2; }\n"
645 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
646 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
647 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
648 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
649 " text.sec { font-size: 10px; }\n"
650 " ]]>\n </style>\n</defs>\n\n");
652 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
653 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
654 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
655 isempty(host
->os_pretty_name
) ? "Linux" : host
->os_pretty_name
,
656 isempty(host
->hostname
) ? "" : host
->hostname
,
657 isempty(host
->kernel_name
) ? "" : host
->kernel_name
,
658 isempty(host
->kernel_release
) ? "" : host
->kernel_release
,
659 isempty(host
->kernel_version
) ? "" : host
->kernel_version
,
660 isempty(host
->architecture
) ? "" : host
->architecture
,
661 isempty(host
->virtualization
) ? "" : host
->virtualization
);
663 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
664 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
666 if (boot
->firmware_time
) {
667 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
668 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
671 if (boot
->loader_time
) {
672 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
673 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
676 if (boot
->kernel_time
) {
677 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
678 svg_text(true, 0, y
, "kernel");
681 if (boot
->initrd_time
) {
682 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
683 svg_text(true, boot
->initrd_time
, y
, "initrd");
686 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
687 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
688 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
689 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
690 svg_text(true, boot
->userspace_time
, y
, "systemd");
693 for (u
= times
; u
< times
+ n
; u
++) {
694 char ts
[FORMAT_TIMESPAN_MAX
];
700 svg_bar("activating", u
->activating
, u
->activated
, y
);
701 svg_bar("active", u
->activated
, u
->deactivating
, y
);
702 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
704 /* place the text on the left if we have passed the half of the svg width */
705 b
= u
->activating
* SCALE_X
< width
/ 2;
707 svg_text(b
, u
->activating
, y
, "%s (%s)",
708 u
->name
, format_timespan(ts
, sizeof(ts
), u
->time
, USEC_PER_MSEC
));
710 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
717 svg("<g transform=\"translate(20,100)\">\n");
719 svg_bar("activating", 0, 300000, y
);
720 svg_text(true, 400000, y
, "Activating");
722 svg_bar("active", 0, 300000, y
);
723 svg_text(true, 400000, y
, "Active");
725 svg_bar("deactivating", 0, 300000, y
);
726 svg_text(true, 400000, y
, "Deactivating");
728 svg_bar("security", 0, 300000, y
);
729 svg_text(true, 400000, y
, "Setting up security module");
731 svg_bar("generators", 0, 300000, y
);
732 svg_text(true, 400000, y
, "Generators");
734 svg_bar("unitsload", 0, 300000, y
);
735 svg_text(true, 400000, y
, "Loading unit files");
742 free_unit_times(times
, (unsigned) n
);
746 free_host_info(host
);
750 static int list_dependencies_print(const char *name
, unsigned int level
, unsigned int branches
,
751 bool last
, struct unit_times
*times
, struct boot_times
*boot
) {
753 char ts
[FORMAT_TIMESPAN_MAX
], ts2
[FORMAT_TIMESPAN_MAX
];
755 for (i
= level
; i
!= 0; i
--)
756 printf("%s", draw_special_char(branches
& (1 << (i
-1)) ? DRAW_TREE_VERTICAL
: DRAW_TREE_SPACE
));
758 printf("%s", draw_special_char(last
? DRAW_TREE_RIGHT
: DRAW_TREE_BRANCH
));
762 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON
, name
,
763 format_timespan(ts
, sizeof(ts
), times
->activating
- boot
->userspace_time
, USEC_PER_MSEC
),
764 format_timespan(ts2
, sizeof(ts2
), times
->time
, USEC_PER_MSEC
), ANSI_HIGHLIGHT_OFF
);
765 else if (times
->activated
> boot
->userspace_time
)
766 printf("%s @%s", name
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
776 static int list_dependencies_get_dependencies(sd_bus
*bus
, const char *name
, char ***deps
) {
777 _cleanup_free_
char *path
= NULL
;
783 path
= unit_dbus_path_from_name(name
);
787 return bus_get_unit_property_strv(bus
, path
, "After", deps
);
790 static Hashmap
*unit_times_hashmap
;
792 static int list_dependencies_compare(const void *_a
, const void *_b
) {
793 const char **a
= (const char**) _a
, **b
= (const char**) _b
;
794 usec_t usa
= 0, usb
= 0;
795 struct unit_times
*times
;
797 times
= hashmap_get(unit_times_hashmap
, *a
);
799 usa
= times
->activated
;
800 times
= hashmap_get(unit_times_hashmap
, *b
);
802 usb
= times
->activated
;
807 static int list_dependencies_one(sd_bus
*bus
, const char *name
, unsigned int level
, char ***units
,
808 unsigned int branches
) {
809 _cleanup_strv_free_
char **deps
= NULL
;
812 usec_t service_longest
= 0;
814 struct unit_times
*times
;
815 struct boot_times
*boot
;
817 if (strv_extend(units
, name
))
820 r
= list_dependencies_get_dependencies(bus
, name
, &deps
);
824 qsort_safe(deps
, strv_length(deps
), sizeof (char*), list_dependencies_compare
);
826 r
= acquire_boot_times(bus
, &boot
);
830 STRV_FOREACH(c
, deps
) {
831 times
= hashmap_get(unit_times_hashmap
, *c
);
834 && times
->activated
<= boot
->finish_time
835 && (times
->activated
>= service_longest
836 || service_longest
== 0)) {
837 service_longest
= times
->activated
;
842 if (service_longest
== 0 )
845 STRV_FOREACH(c
, deps
) {
846 times
= hashmap_get(unit_times_hashmap
, *c
);
847 if (times
&& times
->activated
&& times
->activated
<= boot
->finish_time
&& (service_longest
- times
->activated
) <= arg_fuzz
)
854 STRV_FOREACH(c
, deps
) {
855 times
= hashmap_get(unit_times_hashmap
, *c
);
858 || times
->activated
> boot
->finish_time
859 || service_longest
- times
->activated
> arg_fuzz
)
864 r
= list_dependencies_print(*c
, level
, branches
, to_print
== 0, times
, boot
);
868 if (strv_contains(*units
, *c
)) {
869 r
= list_dependencies_print("...", level
+ 1, (branches
<< 1) | (to_print
? 1 : 0),
876 r
= list_dependencies_one(bus
, *c
, level
+ 1, units
,
877 (branches
<< 1) | (to_print
? 1 : 0));
887 static int list_dependencies(sd_bus
*bus
, const char *name
) {
888 _cleanup_strv_free_
char **units
= NULL
;
889 char ts
[FORMAT_TIMESPAN_MAX
];
890 struct unit_times
*times
;
893 _cleanup_free_
char *path
= NULL
;
894 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
895 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
896 struct boot_times
*boot
;
900 path
= unit_dbus_path_from_name(name
);
904 r
= sd_bus_get_property(
906 "org.freedesktop.systemd1",
908 "org.freedesktop.systemd1.Unit",
914 log_error("Failed to get ID: %s", bus_error_message(&error
, -r
));
918 r
= sd_bus_message_read(reply
, "s", &id
);
920 return bus_log_parse_error(r
);
922 times
= hashmap_get(unit_times_hashmap
, id
);
924 r
= acquire_boot_times(bus
, &boot
);
930 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON
, id
,
931 format_timespan(ts
, sizeof(ts
), times
->time
, USEC_PER_MSEC
), ANSI_HIGHLIGHT_OFF
);
932 else if (times
->activated
> boot
->userspace_time
)
933 printf("%s @%s\n", id
, format_timespan(ts
, sizeof(ts
), times
->activated
- boot
->userspace_time
, USEC_PER_MSEC
));
938 return list_dependencies_one(bus
, name
, 0, &units
, 0);
941 static int analyze_critical_chain(sd_bus
*bus
, char *names
[]) {
942 struct unit_times
*times
;
947 n
= acquire_time_data(bus
, ×
);
951 h
= hashmap_new(&string_hash_ops
);
955 for (i
= 0; i
< (unsigned)n
; i
++) {
956 r
= hashmap_put(h
, times
[i
].name
, ×
[i
]);
960 unit_times_hashmap
= h
;
962 pager_open_if_enabled();
964 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
965 "The time the unit takes to start is printed after the \"+\" character.\n");
967 if (!strv_isempty(names
)) {
969 STRV_FOREACH(name
, names
)
970 list_dependencies(bus
, *name
);
972 list_dependencies(bus
, SPECIAL_DEFAULT_TARGET
);
975 free_unit_times(times
, (unsigned) n
);
979 static int analyze_blame(sd_bus
*bus
) {
980 struct unit_times
*times
;
984 n
= acquire_time_data(bus
, ×
);
988 qsort(times
, n
, sizeof(struct unit_times
), compare_unit_time
);
990 pager_open_if_enabled();
992 for (i
= 0; i
< (unsigned) n
; i
++) {
993 char ts
[FORMAT_TIMESPAN_MAX
];
995 if (times
[i
].time
> 0)
996 printf("%16s %s\n", format_timespan(ts
, sizeof(ts
), times
[i
].time
, USEC_PER_MSEC
), times
[i
].name
);
999 free_unit_times(times
, (unsigned) n
);
1003 static int analyze_time(sd_bus
*bus
) {
1004 _cleanup_free_
char *buf
= NULL
;
1007 r
= pretty_boot_time(bus
, &buf
);
1015 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
[]) {
1016 _cleanup_strv_free_
char **units
= NULL
;
1019 bool match_patterns
;
1025 match_patterns
= strv_fnmatch(patterns
, u
->id
, 0);
1027 if (!strv_isempty(from_patterns
) &&
1029 !strv_fnmatch(from_patterns
, u
->id
, 0))
1032 r
= bus_get_unit_property_strv(bus
, u
->unit_path
, prop
, &units
);
1036 STRV_FOREACH(unit
, units
) {
1037 bool match_patterns2
;
1039 match_patterns2
= strv_fnmatch(patterns
, *unit
, 0);
1041 if (!strv_isempty(to_patterns
) &&
1043 !strv_fnmatch(to_patterns
, *unit
, 0))
1046 if (!strv_isempty(patterns
) && !match_patterns
&& !match_patterns2
)
1049 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u
->id
, *unit
, color
);
1055 static int graph_one(sd_bus
*bus
, const UnitInfo
*u
, char *patterns
[], char *from_patterns
[], char *to_patterns
[]) {
1061 if (arg_dot
== DEP_ORDER
||arg_dot
== DEP_ALL
) {
1062 r
= graph_one_property(bus
, u
, "After", "green", patterns
, from_patterns
, to_patterns
);
1067 if (arg_dot
== DEP_REQUIRE
||arg_dot
== DEP_ALL
) {
1068 r
= graph_one_property(bus
, u
, "Requires", "black", patterns
, from_patterns
, to_patterns
);
1071 r
= graph_one_property(bus
, u
, "RequiresOverridable", "black", patterns
, from_patterns
, to_patterns
);
1074 r
= graph_one_property(bus
, u
, "RequisiteOverridable", "darkblue", patterns
, from_patterns
, to_patterns
);
1077 r
= graph_one_property(bus
, u
, "Wants", "grey66", patterns
, from_patterns
, to_patterns
);
1080 r
= graph_one_property(bus
, u
, "Conflicts", "red", patterns
, from_patterns
, to_patterns
);
1083 r
= graph_one_property(bus
, u
, "ConflictedBy", "red", patterns
, from_patterns
, to_patterns
);
1091 static int expand_patterns(sd_bus
*bus
, char **patterns
, char ***ret
) {
1092 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1096 STRV_FOREACH(pattern
, patterns
) {
1097 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1098 _cleanup_free_
char *unit
= NULL
, *unit_id
= NULL
;
1100 if (strv_extend(&expanded_patterns
, *pattern
) < 0)
1103 if (string_is_glob(*pattern
))
1106 unit
= unit_dbus_path_from_name(*pattern
);
1110 r
= sd_bus_get_property_string(
1112 "org.freedesktop.systemd1",
1114 "org.freedesktop.systemd1.Unit",
1119 return log_error_errno(r
, "Failed to get ID: %s", bus_error_message(&error
, r
));
1121 if (!streq(*pattern
, unit_id
)) {
1122 if (strv_extend(&expanded_patterns
, unit_id
) < 0)
1127 *ret
= expanded_patterns
;
1128 expanded_patterns
= NULL
; /* do not free */
1133 static int dot(sd_bus
*bus
, char* patterns
[]) {
1134 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1135 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1136 _cleanup_strv_free_
char **expanded_patterns
= NULL
;
1137 _cleanup_strv_free_
char **expanded_from_patterns
= NULL
;
1138 _cleanup_strv_free_
char **expanded_to_patterns
= NULL
;
1142 r
= expand_patterns(bus
, patterns
, &expanded_patterns
);
1146 r
= expand_patterns(bus
, arg_dot_from_patterns
, &expanded_from_patterns
);
1150 r
= expand_patterns(bus
, arg_dot_to_patterns
, &expanded_to_patterns
);
1154 r
= sd_bus_call_method(
1156 "org.freedesktop.systemd1",
1157 "/org/freedesktop/systemd1",
1158 "org.freedesktop.systemd1.Manager",
1164 log_error("Failed to list units: %s", bus_error_message(&error
, -r
));
1168 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
1170 return bus_log_parse_error(r
);
1172 printf("digraph systemd {\n");
1174 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
1176 r
= graph_one(bus
, &u
, expanded_patterns
, expanded_from_patterns
, expanded_to_patterns
);
1181 return bus_log_parse_error(r
);
1185 log_info(" Color legend: black = Requires\n"
1186 " dark blue = Requisite\n"
1187 " dark grey = Wants\n"
1188 " red = Conflicts\n"
1189 " green = After\n");
1192 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1193 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1198 static int dump(sd_bus
*bus
, char **args
) {
1199 _cleanup_bus_message_unref_ sd_bus_message
*reply
= NULL
;
1200 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1201 const char *text
= NULL
;
1204 if (!strv_isempty(args
)) {
1205 log_error("Too many arguments.");
1209 pager_open_if_enabled();
1211 r
= sd_bus_call_method(
1213 "org.freedesktop.systemd1",
1214 "/org/freedesktop/systemd1",
1215 "org.freedesktop.systemd1.Manager",
1221 log_error("Failed issue method call: %s", bus_error_message(&error
, -r
));
1225 r
= sd_bus_message_read(reply
, "s", &text
);
1227 return bus_log_parse_error(r
);
1229 fputs(text
, stdout
);
1233 static int set_log_level(sd_bus
*bus
, char **args
) {
1234 _cleanup_bus_error_free_ sd_bus_error error
= SD_BUS_ERROR_NULL
;
1240 if (strv_length(args
) != 1) {
1241 log_error("This command expects one argument only.");
1245 r
= sd_bus_set_property(
1247 "org.freedesktop.systemd1",
1248 "/org/freedesktop/systemd1",
1249 "org.freedesktop.systemd1.Manager",
1255 log_error("Failed to issue method call: %s", bus_error_message(&error
, -r
));
1262 static void help(void) {
1264 pager_open_if_enabled();
1266 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1267 "Profile systemd, show unit dependencies, check unit files.\n\n"
1268 " -h --help Show this help\n"
1269 " --version Show package version\n"
1270 " --no-pager Do not pipe output into a pager\n"
1271 " --system Operate on system systemd instance\n"
1272 " --user Operate on user systemd instance\n"
1273 " -H --host=[USER@]HOST Operate on remote host\n"
1274 " -M --machine=CONTAINER Operate on local container\n"
1275 " --order Show only order in the graph\n"
1276 " --require Show only requirement in the graph\n"
1277 " --from-pattern=GLOB Show only origins in the graph\n"
1278 " --to-pattern=GLOB Show only destinations in the graph\n"
1279 " --fuzz=SECONDS Also print also services which finished SECONDS\n"
1280 " earlier than the latest in the branch\n"
1281 " --man[=BOOL] Do [not] check for existence of man pages\n\n"
1283 " time Print time spent in the kernel\n"
1284 " blame Print list of running units ordered by time to init\n"
1285 " critical-chain Print a tree of the time critical chain of units\n"
1286 " plot Output SVG graphic showing service initialization\n"
1287 " dot Output dependency graph in dot(1) format\n"
1288 " set-log-level LEVEL Set logging threshold for systemd\n"
1289 " dump Output state serialization of service manager\n"
1290 " verify FILE... Check unit files for correctness\n"
1291 , program_invocation_short_name
);
1293 /* When updating this list, including descriptions, apply
1294 * changes to shell-completion/bash/systemd-analyze and
1295 * shell-completion/zsh/_systemd-analyze too. */
1298 static int parse_argv(int argc
, char *argv
[]) {
1300 ARG_VERSION
= 0x100,
1305 ARG_DOT_FROM_PATTERN
,
1312 static const struct option options
[] = {
1313 { "help", no_argument
, NULL
, 'h' },
1314 { "version", no_argument
, NULL
, ARG_VERSION
},
1315 { "order", no_argument
, NULL
, ARG_ORDER
},
1316 { "require", no_argument
, NULL
, ARG_REQUIRE
},
1317 { "user", no_argument
, NULL
, ARG_USER
},
1318 { "system", no_argument
, NULL
, ARG_SYSTEM
},
1319 { "from-pattern", required_argument
, NULL
, ARG_DOT_FROM_PATTERN
},
1320 { "to-pattern", required_argument
, NULL
, ARG_DOT_TO_PATTERN
},
1321 { "fuzz", required_argument
, NULL
, ARG_FUZZ
},
1322 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1323 { "man", optional_argument
, NULL
, ARG_MAN
},
1324 { "host", required_argument
, NULL
, 'H' },
1325 { "machine", required_argument
, NULL
, 'M' },
1334 while ((c
= getopt_long(argc
, argv
, "hH:M:", options
, NULL
)) >= 0)
1342 puts(PACKAGE_STRING
);
1343 puts(SYSTEMD_FEATURES
);
1355 arg_dot
= DEP_ORDER
;
1359 arg_dot
= DEP_REQUIRE
;
1362 case ARG_DOT_FROM_PATTERN
:
1363 if (strv_extend(&arg_dot_from_patterns
, optarg
) < 0)
1368 case ARG_DOT_TO_PATTERN
:
1369 if (strv_extend(&arg_dot_to_patterns
, optarg
) < 0)
1375 r
= parse_sec(optarg
, &arg_fuzz
);
1381 arg_no_pager
= true;
1385 arg_transport
= BUS_TRANSPORT_REMOTE
;
1390 arg_transport
= BUS_TRANSPORT_MACHINE
;
1396 r
= parse_boolean(optarg
);
1398 log_error("Failed to parse --man= argument.");
1412 assert_not_reached("Unhandled option code.");
1415 return 1; /* work to do */
1418 int main(int argc
, char *argv
[]) {
1421 setlocale(LC_ALL
, "");
1422 setlocale(LC_NUMERIC
, "C"); /* we want to format/parse floats in C style */
1423 log_parse_environment();
1426 r
= parse_argv(argc
, argv
);
1430 if (streq_ptr(argv
[optind
], "verify"))
1431 r
= verify_units(argv
+optind
+1,
1432 arg_user
? MANAGER_USER
: MANAGER_SYSTEM
,
1435 _cleanup_bus_flush_close_unref_ sd_bus
*bus
= NULL
;
1437 r
= bus_open_transport_systemd(arg_transport
, arg_host
, arg_user
, &bus
);
1439 log_error_errno(r
, "Failed to create bus connection: %m");
1443 if (!argv
[optind
] || streq(argv
[optind
], "time"))
1444 r
= analyze_time(bus
);
1445 else if (streq(argv
[optind
], "blame"))
1446 r
= analyze_blame(bus
);
1447 else if (streq(argv
[optind
], "critical-chain"))
1448 r
= analyze_critical_chain(bus
, argv
+optind
+1);
1449 else if (streq(argv
[optind
], "plot"))
1450 r
= analyze_plot(bus
);
1451 else if (streq(argv
[optind
], "dot"))
1452 r
= dot(bus
, argv
+optind
+1);
1453 else if (streq(argv
[optind
], "dump"))
1454 r
= dump(bus
, argv
+optind
+1);
1455 else if (streq(argv
[optind
], "set-log-level"))
1456 r
= set_log_level(bus
, argv
+optind
+1);
1458 log_error("Unknown operation '%s'.", argv
[optind
]);
1464 strv_free(arg_dot_from_patterns
);
1465 strv_free(arg_dot_to_patterns
);
1467 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;