]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze-plot.c
Merge pull request #25608 from poettering/dissect-moar
[thirdparty/systemd.git] / src / analyze / analyze-plot.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "analyze.h"
4 #include "analyze-plot.h"
5 #include "analyze-time-data.h"
6 #include "bus-error.h"
7 #include "bus-map-properties.h"
8 #include "format-table.h"
9 #include "os-util.h"
10 #include "sort-util.h"
11 #include "version.h"
12
13 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
14 #define SCALE_Y (20.0)
15
16 #define svg(...) printf(__VA_ARGS__)
17
18 #define svg_bar(class, x1, x2, y) \
19 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
20 (class), \
21 SCALE_X * (x1), SCALE_Y * (y), \
22 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
23
24 #define svg_text(b, x, y, format, ...) \
25 do { \
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__); \
28 svg("</text>\n"); \
29 } while (false)
30
31
32 typedef struct HostInfo {
33 char *hostname;
34 char *kernel_name;
35 char *kernel_release;
36 char *kernel_version;
37 char *os_pretty_name;
38 char *virtualization;
39 char *architecture;
40 } HostInfo;
41
42 static HostInfo *free_host_info(HostInfo *hi) {
43 if (!hi)
44 return NULL;
45
46 free(hi->hostname);
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);
53 return mfree(hi);
54 }
55
56 DEFINE_TRIVIAL_CLEANUP_FUNC(HostInfo *, free_host_info);
57
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) },
65 {}
66 };
67
68 static const struct bus_properties_map manager_map[] = {
69 { "Virtualization", "s", NULL, offsetof(HostInfo, virtualization) },
70 { "Architecture", "s", NULL, offsetof(HostInfo, architecture) },
71 {}
72 };
73
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;
77 int r;
78
79 host = new0(HostInfo, 1);
80 if (!host)
81 return log_oom();
82
83 if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) {
84 r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &system_bus);
85 if (r < 0) {
86 log_debug_errno(r, "Failed to connect to system bus, ignoring: %m");
87 goto manager;
88 }
89 }
90
91 r = bus_map_all_properties(
92 system_bus ?: bus,
93 "org.freedesktop.hostname1",
94 "/org/freedesktop/hostname1",
95 hostname_map,
96 BUS_MAP_STRDUP,
97 &error,
98 NULL,
99 host);
100 if (r < 0) {
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);
104 }
105
106 manager:
107 r = bus_map_all_properties(
108 bus,
109 "org.freedesktop.systemd1",
110 "/org/freedesktop/systemd1",
111 manager_map,
112 BUS_MAP_STRDUP,
113 &error,
114 NULL,
115 host);
116 if (r < 0)
117 return log_error_errno(r, "Failed to get host information from systemd: %s",
118 bus_error_message(&error, r));
119
120 *hi = TAKE_PTR(host);
121 return 0;
122 }
123
124 static int compare_unit_start(const UnitTimes *a, const UnitTimes *b) {
125 return CMP(a->activating, b->activating);
126 }
127
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),
132 SCALE_Y * height);
133
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",
139 SCALE_X * i,
140 SCALE_X * i,
141 SCALE_Y * height,
142 SCALE_X * i,
143 -5.0,
144 0.000001 * i);
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",
148 SCALE_X * i,
149 SCALE_X * i,
150 SCALE_Y * height,
151 SCALE_X * i,
152 -5.0,
153 0.000001 * i);
154 else
155 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
156 SCALE_X * i,
157 SCALE_X * i,
158 SCALE_Y * height);
159 }
160 }
161 static int plot_unit_times(UnitTimes *u, double width, int y) {
162 bool b;
163
164 if (!u->name)
165 return 0;
166
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);
170
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;
173 if (u->time)
174 svg_text(b, u->activating, y, "%s (%s)",
175 u->name, FORMAT_TIMESPAN(u->time, USEC_PER_MSEC));
176 else
177 svg_text(b, u->activating, y, "%s", u->name);
178
179 return 1;
180 }
181
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;
192 }
193
194 static int produce_plot_as_svg(
195 UnitTimes *times,
196 const HostInfo *host,
197 const BootTimes *boot,
198 const char *pretty_times) {
199 int m = 1, y = 0;
200 UnitTimes *u;
201 double width;
202
203 width = SCALE_X * (boot->firmware_time + boot->finish_time);
204 if (width < 800.0)
205 width = 800.0;
206
207 if (boot->firmware_time > boot->loader_time)
208 m++;
209 if (boot->loader_time > 0) {
210 m++;
211 if (width < 1000.0)
212 width = 1000.0;
213 }
214 if (boot->initrd_time > 0)
215 m++;
216 if (boot->kernel_done_time > 0)
217 m++;
218
219 for (u = times; u->has_data; u++) {
220 double text_start, text_width;
221
222 if (u->activating > boot->finish_time) {
223 u->name = mfree(u->name);
224 continue;
225 }
226
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;
234
235 limit_times_to_boot(boot, u);
236
237 m++;
238 }
239
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");
243
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 */);
248
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);
256
257 /* style sheet */
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"
274 "// line.sec1 { }\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");
282
283 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
284 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
285 if (host)
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));
294
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);
297
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");
301 y++;
302 }
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");
306 y++;
307 }
308 if (boot->kernel_done_time > 0) {
309 svg_bar("kernel", 0, boot->kernel_done_time, y);
310 svg_text(true, 0, y, "kernel");
311 y++;
312 }
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");
322 y++;
323 }
324
325 for (u = times; u->has_data; u++) {
326 if (u->activating >= boot->userspace_time)
327 break;
328
329 y += plot_unit_times(u, width, y);
330 }
331
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");
338 y++;
339
340 for (; u->has_data; u++)
341 y += plot_unit_times(u, width, y);
342
343 svg("</g>\n");
344
345 /* Legend */
346 svg("<g transform=\"translate(20,100)\">\n");
347 y++;
348 svg_bar("activating", 0, 300000, y);
349 svg_text(true, 400000, y, "Activating");
350 y++;
351 svg_bar("active", 0, 300000, y);
352 svg_text(true, 400000, y, "Active");
353 y++;
354 svg_bar("deactivating", 0, 300000, y);
355 svg_text(true, 400000, y, "Deactivating");
356 y++;
357 if (boot->security_start_time > 0) {
358 svg_bar("security", 0, 300000, y);
359 svg_text(true, 400000, y, "Setting up security module");
360 y++;
361 }
362 svg_bar("generators", 0, 300000, y);
363 svg_text(true, 400000, y, "Generators");
364 y++;
365 svg_bar("unitsload", 0, 300000, y);
366 svg_text(true, 400000, y, "Loading unit files");
367 y++;
368
369 svg("</g>\n\n");
370
371 svg("</svg>\n");
372
373 return 0;
374 }
375
376 static int show_table(Table *table, const char *word) {
377 int r;
378
379 assert(table);
380 assert(word);
381
382 if (table_get_rows(table) > 1) {
383 table_set_header(table, arg_legend);
384
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);
387 else
388 r = table_print(table, NULL);
389 if (r < 0)
390 return table_log_print_error(r);
391 }
392
393 if (arg_legend) {
394 if (table_get_rows(table) > 1)
395 printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
396 else
397 printf("No %s.\n", word);
398 }
399
400 return 0;
401 }
402
403 static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) {
404 _cleanup_(table_unrefp) Table *table = NULL;
405 int r;
406
407 table = table_new("name", "activated", "activating", "time", "deactivated", "deactivating");
408 if (!table)
409 return log_oom();
410
411 for (; times->has_data; times++) {
412 limit_times_to_boot(boot, times);
413
414 r = table_add_many(
415 table,
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);
422 if (r < 0)
423 return table_log_add_error(r);
424 }
425
426 return show_table(table, "Units");
427 }
428
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;
435 BootTimes *boot;
436 int n, r;
437
438 r = acquire_bus(&bus, &use_full_bus);
439 if (r < 0)
440 return bus_log_connect_error(r, arg_transport);
441
442 n = acquire_boot_times(bus, /* require_finished = */ true, &boot);
443 if (n < 0)
444 return n;
445
446 n = pretty_boot_time(bus, &pretty_times);
447 if (n < 0)
448 return n;
449
450 if (use_full_bus || arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) {
451 n = acquire_host_info(bus, &host);
452 if (n < 0)
453 return n;
454 }
455
456 n = acquire_time_data(bus, /* require_finished = */ true, &times);
457 if (n <= 0)
458 return n;
459
460 typesafe_qsort(times, n, compare_unit_start);
461
462 if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) || arg_table)
463 r = produce_plot_as_text(times, boot);
464 else
465 r = produce_plot_as_svg(times, host, boot, pretty_times);
466 if (r < 0)
467 return r;
468
469 return EXIT_SUCCESS;
470 }