1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
4 #include "analyze-plot.h"
5 #include "analyze-time-data.h"
7 #include "bus-map-properties.h"
8 #include "format-table.h"
10 #include "sort-util.h"
13 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
14 #define SCALE_Y (20.0)
16 #define svg(...) printf(__VA_ARGS__)
18 #define svg_bar(class, x1, x2, y) \
19 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
21 SCALE_X * (x1), SCALE_Y * (y), \
22 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
24 #define svg_text(b, x, y, format, ...) \
26 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
27 svg(format, ## __VA_ARGS__); \
32 typedef struct HostInfo
{
42 static HostInfo
*free_host_info(HostInfo
*hi
) {
47 free(hi
->kernel_name
);
48 free(hi
->kernel_release
);
49 free(hi
->kernel_version
);
50 free(hi
->os_pretty_name
);
51 free(hi
->virtualization
);
52 free(hi
->architecture
);
56 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo
*, free_host_info
);
58 static int acquire_host_info(sd_bus
*bus
, HostInfo
**hi
) {
59 static const struct bus_properties_map hostname_map
[] = {
60 { "Hostname", "s", NULL
, offsetof(HostInfo
, hostname
) },
61 { "KernelName", "s", NULL
, offsetof(HostInfo
, kernel_name
) },
62 { "KernelRelease", "s", NULL
, offsetof(HostInfo
, kernel_release
) },
63 { "KernelVersion", "s", NULL
, offsetof(HostInfo
, kernel_version
) },
64 { "OperatingSystemPrettyName", "s", NULL
, offsetof(HostInfo
, os_pretty_name
) },
68 static const struct bus_properties_map manager_map
[] = {
69 { "Virtualization", "s", NULL
, offsetof(HostInfo
, virtualization
) },
70 { "Architecture", "s", NULL
, offsetof(HostInfo
, architecture
) },
74 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
75 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*system_bus
= NULL
;
76 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
79 host
= new0(HostInfo
, 1);
83 if (arg_runtime_scope
!= RUNTIME_SCOPE_SYSTEM
) {
84 r
= bus_connect_transport(arg_transport
, arg_host
, RUNTIME_SCOPE_SYSTEM
, &system_bus
);
86 log_debug_errno(r
, "Failed to connect to system bus, ignoring: %m");
91 r
= bus_map_all_properties(
93 "org.freedesktop.hostname1",
94 "/org/freedesktop/hostname1",
101 log_debug_errno(r
, "Failed to get host information from systemd-hostnamed, ignoring: %s",
102 bus_error_message(&error
, r
));
103 sd_bus_error_free(&error
);
107 r
= bus_map_all_properties(
109 "org.freedesktop.systemd1",
110 "/org/freedesktop/systemd1",
117 return log_error_errno(r
, "Failed to get host information from systemd: %s",
118 bus_error_message(&error
, r
));
120 *hi
= TAKE_PTR(host
);
124 static int compare_unit_start(const UnitTimes
*a
, const UnitTimes
*b
) {
125 return CMP(a
->activating
, b
->activating
);
128 static void svg_graph_box(double height
, double begin
, double end
) {
129 /* outside box, fill */
130 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
131 SCALE_X
* (end
- begin
),
134 for (long long i
= ((long long) (begin
/ 100000)) * 100000; i
<= end
; i
+= 100000) {
135 /* lines for each second */
136 if (i
% 5000000 == 0)
137 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
138 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
145 else if (i
% 1000000 == 0)
146 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
147 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
155 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
161 static int plot_unit_times(UnitTimes
*u
, double width
, int y
) {
167 svg_bar("activating", u
->activating
, u
->activated
, y
);
168 svg_bar("active", u
->activated
, u
->deactivating
, y
);
169 svg_bar("deactivating", u
->deactivating
, u
->deactivated
, y
);
171 /* place the text on the left if we have passed the half of the svg width */
172 b
= u
->activating
* SCALE_X
< width
/ 2;
174 svg_text(b
, u
->activating
, y
, "%s (%s)",
175 u
->name
, FORMAT_TIMESPAN(u
->time
, USEC_PER_MSEC
));
177 svg_text(b
, u
->activating
, y
, "%s", u
->name
);
182 static void limit_times_to_boot(const BootTimes
*boot
, UnitTimes
*u
) {
183 if (u
->deactivated
> u
->activating
&& u
->deactivated
<= boot
->finish_time
&& u
->activated
== 0
184 && u
->deactivating
== 0)
185 u
->activated
= u
->deactivating
= u
->deactivated
;
186 if (u
->activated
< u
->activating
|| u
->activated
> boot
->finish_time
)
187 u
->activated
= boot
->finish_time
;
188 if (u
->deactivating
< u
->activated
|| u
->deactivating
> boot
->finish_time
)
189 u
->deactivating
= boot
->finish_time
;
190 if (u
->deactivated
< u
->deactivating
|| u
->deactivated
> boot
->finish_time
)
191 u
->deactivated
= boot
->finish_time
;
194 static int produce_plot_as_svg(
196 const HostInfo
*host
,
197 const BootTimes
*boot
,
198 const char *pretty_times
) {
203 width
= SCALE_X
* (boot
->firmware_time
+ boot
->finish_time
);
207 if (boot
->firmware_time
> boot
->loader_time
)
209 if (boot
->loader_time
> 0) {
214 if (boot
->initrd_time
> 0)
216 if (boot
->kernel_done_time
> 0)
219 for (u
= times
; u
->has_data
; u
++) {
220 double text_start
, text_width
;
222 if (u
->activating
> boot
->finish_time
) {
223 u
->name
= mfree(u
->name
);
227 /* If the text cannot fit on the left side then
228 * increase the svg width so it fits on the right.
229 * TODO: calculate the text width more accurately */
230 text_width
= 8.0 * strlen(u
->name
);
231 text_start
= (boot
->firmware_time
+ u
->activating
) * SCALE_X
;
232 if (text_width
> text_start
&& text_width
+ text_start
> width
)
233 width
= text_width
+ text_start
;
235 limit_times_to_boot(boot
, u
);
240 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
241 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
242 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
244 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
245 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
246 80.0 + width
, 150.0 + (m
* SCALE_Y
) +
247 5 * SCALE_Y
/* legend */);
249 /* write some basic info as a comment, including some help */
250 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
251 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
252 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
253 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
254 "<!-- point your browser to this file. -->\n\n"
255 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", GIT_VERSION
);
258 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
259 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
260 " rect.background { fill: rgb(255,255,255); }\n"
261 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
262 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
263 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
264 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
265 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
266 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
267 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
268 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
269 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
270 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
271 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
272 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
273 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
275 " line.sec5 { stroke-width: 2; }\n"
276 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
277 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
278 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
279 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
280 " text.sec { font-size: 10px; }\n"
281 " ]]>\n </style>\n</defs>\n\n");
283 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
284 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times
);
286 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
287 os_release_pretty_name(host
->os_pretty_name
, NULL
),
288 strempty(host
->hostname
),
289 strempty(host
->kernel_name
),
290 strempty(host
->kernel_release
),
291 strempty(host
->kernel_version
),
292 strempty(host
->architecture
),
293 strempty(host
->virtualization
));
295 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X
* boot
->firmware_time
));
296 svg_graph_box(m
, -(double) boot
->firmware_time
, boot
->finish_time
);
298 if (boot
->firmware_time
> 0) {
299 svg_bar("firmware", -(double) boot
->firmware_time
, -(double) boot
->loader_time
, y
);
300 svg_text(true, -(double) boot
->firmware_time
, y
, "firmware");
303 if (boot
->loader_time
> 0) {
304 svg_bar("loader", -(double) boot
->loader_time
, 0, y
);
305 svg_text(true, -(double) boot
->loader_time
, y
, "loader");
308 if (boot
->kernel_done_time
> 0) {
309 svg_bar("kernel", 0, boot
->kernel_done_time
, y
);
310 svg_text(true, 0, y
, "kernel");
313 if (boot
->initrd_time
> 0) {
314 svg_bar("initrd", boot
->initrd_time
, boot
->userspace_time
, y
);
315 if (boot
->initrd_security_start_time
< boot
->initrd_security_finish_time
)
316 svg_bar("security", boot
->initrd_security_start_time
, boot
->initrd_security_finish_time
, y
);
317 if (boot
->initrd_generators_start_time
< boot
->initrd_generators_finish_time
)
318 svg_bar("generators", boot
->initrd_generators_start_time
, boot
->initrd_generators_finish_time
, y
);
319 if (boot
->initrd_unitsload_start_time
< boot
->initrd_unitsload_finish_time
)
320 svg_bar("unitsload", boot
->initrd_unitsload_start_time
, boot
->initrd_unitsload_finish_time
, y
);
321 svg_text(true, boot
->initrd_time
, y
, "initrd");
325 for (u
= times
; u
->has_data
; u
++) {
326 if (u
->activating
>= boot
->userspace_time
)
329 y
+= plot_unit_times(u
, width
, y
);
332 svg_bar("active", boot
->userspace_time
, boot
->finish_time
, y
);
333 if (boot
->security_start_time
> 0)
334 svg_bar("security", boot
->security_start_time
, boot
->security_finish_time
, y
);
335 svg_bar("generators", boot
->generators_start_time
, boot
->generators_finish_time
, y
);
336 svg_bar("unitsload", boot
->unitsload_start_time
, boot
->unitsload_finish_time
, y
);
337 svg_text(true, boot
->userspace_time
, y
, "systemd");
340 for (; u
->has_data
; u
++)
341 y
+= plot_unit_times(u
, width
, y
);
346 svg("<g transform=\"translate(20,100)\">\n");
348 svg_bar("activating", 0, 300000, y
);
349 svg_text(true, 400000, y
, "Activating");
351 svg_bar("active", 0, 300000, y
);
352 svg_text(true, 400000, y
, "Active");
354 svg_bar("deactivating", 0, 300000, y
);
355 svg_text(true, 400000, y
, "Deactivating");
357 if (boot
->security_start_time
> 0) {
358 svg_bar("security", 0, 300000, y
);
359 svg_text(true, 400000, y
, "Setting up security module");
362 svg_bar("generators", 0, 300000, y
);
363 svg_text(true, 400000, y
, "Generators");
365 svg_bar("unitsload", 0, 300000, y
);
366 svg_text(true, 400000, y
, "Loading unit files");
376 static int show_table(Table
*table
, const char *word
) {
382 if (table_get_rows(table
) > 1) {
383 table_set_header(table
, arg_legend
);
385 if (!FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
))
386 r
= table_print_json(table
, NULL
, arg_json_format_flags
| JSON_FORMAT_COLOR_AUTO
);
388 r
= table_print(table
, NULL
);
390 return table_log_print_error(r
);
394 if (table_get_rows(table
) > 1)
395 printf("\n%zu %s listed.\n", table_get_rows(table
) - 1, word
);
397 printf("No %s.\n", word
);
403 static int produce_plot_as_text(UnitTimes
*times
, const BootTimes
*boot
) {
404 _cleanup_(table_unrefp
) Table
*table
= NULL
;
407 table
= table_new("name", "activated", "activating", "time", "deactivated", "deactivating");
411 for (; times
->has_data
; times
++) {
412 limit_times_to_boot(boot
, times
);
416 TABLE_STRING
, times
->name
,
417 TABLE_TIMESPAN_MSEC
, times
->activated
,
418 TABLE_TIMESPAN_MSEC
, times
->activating
,
419 TABLE_TIMESPAN_MSEC
, times
->time
,
420 TABLE_TIMESPAN_MSEC
, times
->deactivated
,
421 TABLE_TIMESPAN_MSEC
, times
->deactivating
);
423 return table_log_add_error(r
);
426 return show_table(table
, "Units");
429 int verb_plot(int argc
, char *argv
[], void *userdata
) {
430 _cleanup_(free_host_infop
) HostInfo
*host
= NULL
;
431 _cleanup_(sd_bus_flush_close_unrefp
) sd_bus
*bus
= NULL
;
432 _cleanup_(unit_times_free_arrayp
) UnitTimes
*times
= NULL
;
433 _cleanup_free_
char *pretty_times
= NULL
;
434 bool use_full_bus
= arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
;
438 r
= acquire_bus(&bus
, &use_full_bus
);
440 return bus_log_connect_error(r
, arg_transport
);
442 n
= acquire_boot_times(bus
, /* require_finished = */ true, &boot
);
446 n
= pretty_boot_time(bus
, &pretty_times
);
450 if (use_full_bus
|| arg_runtime_scope
!= RUNTIME_SCOPE_SYSTEM
) {
451 n
= acquire_host_info(bus
, &host
);
456 n
= acquire_time_data(bus
, /* require_finished = */ true, ×
);
460 typesafe_qsort(times
, n
, compare_unit_start
);
462 if (!FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
) || arg_table
)
463 r
= produce_plot_as_text(times
, boot
);
465 r
= produce_plot_as_svg(times
, host
, boot
, pretty_times
);