]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze.c
analyze: read host and system information from remote
[thirdparty/systemd.git] / src / analyze / analyze.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010-2013 Lennart Poettering
7 Copyright 2013 Simon Peeters
8
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.
13
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.
18
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/>.
21 ***/
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <getopt.h>
26 #include <locale.h>
27 #include <sys/utsname.h>
28 #include <fnmatch.h>
29
30 #include "sd-bus.h"
31 #include "bus-util.h"
32 #include "bus-error.h"
33 #include "install.h"
34 #include "log.h"
35 #include "build.h"
36 #include "util.h"
37 #include "strxcpyx.h"
38 #include "fileio.h"
39 #include "strv.h"
40 #include "unit-name.h"
41 #include "special.h"
42 #include "hashmap.h"
43 #include "pager.h"
44
45 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
46 #define SCALE_Y (20.0)
47
48 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
49
50 #define svg(...) printf(__VA_ARGS__)
51
52 #define svg_bar(class, x1, x2, y) \
53 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
54 (class), \
55 SCALE_X * (x1), SCALE_Y * (y), \
56 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
57
58 #define svg_text(b, x, y, format, ...) \
59 do { \
60 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
61 svg(format, ## __VA_ARGS__); \
62 svg("</text>\n"); \
63 } while(false)
64
65 static enum dot {
66 DEP_ALL,
67 DEP_ORDER,
68 DEP_REQUIRE
69 } arg_dot = DEP_ALL;
70 static char** arg_dot_from_patterns = NULL;
71 static char** arg_dot_to_patterns = NULL;
72 static usec_t arg_fuzz = 0;
73 static bool arg_no_pager = false;
74 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
75 static char *arg_host = NULL;
76 static bool arg_user = false;
77
78 struct boot_times {
79 usec_t firmware_time;
80 usec_t loader_time;
81 usec_t kernel_time;
82 usec_t kernel_done_time;
83 usec_t initrd_time;
84 usec_t userspace_time;
85 usec_t finish_time;
86 usec_t security_start_time;
87 usec_t security_finish_time;
88 usec_t generators_start_time;
89 usec_t generators_finish_time;
90 usec_t unitsload_start_time;
91 usec_t unitsload_finish_time;
92 };
93
94 struct unit_times {
95 char *name;
96 usec_t activating;
97 usec_t activated;
98 usec_t deactivated;
99 usec_t deactivating;
100 usec_t time;
101 };
102
103 struct host_info {
104 char *hostname;
105 char *kernel_name;
106 char *kernel_release;
107 char *kernel_version;
108 char *os_pretty_name;
109 char *virtualization;
110 char *architecture;
111 };
112
113 static void pager_open_if_enabled(void) {
114
115 if (arg_no_pager)
116 return;
117
118 pager_open(false);
119 }
120
121 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
122 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
123 int r;
124
125 assert(bus);
126 assert(path);
127 assert(interface);
128 assert(property);
129 assert(val);
130
131 r = sd_bus_get_property_trivial(
132 bus,
133 "org.freedesktop.systemd1",
134 path,
135 interface,
136 property,
137 &error,
138 't', val);
139
140 if (r < 0) {
141 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
142 return r;
143 }
144
145 return 0;
146 }
147
148 static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
149 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
150 int r;
151
152 assert(bus);
153 assert(path);
154 assert(property);
155 assert(strv);
156
157 r = sd_bus_get_property_strv(
158 bus,
159 "org.freedesktop.systemd1",
160 path,
161 "org.freedesktop.systemd1.Unit",
162 property,
163 &error,
164 strv);
165 if (r < 0) {
166 log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
167 return r;
168 }
169
170 return 0;
171 }
172
173 static int compare_unit_time(const void *a, const void *b) {
174 return compare(((struct unit_times *)b)->time,
175 ((struct unit_times *)a)->time);
176 }
177
178 static int compare_unit_start(const void *a, const void *b) {
179 return compare(((struct unit_times *)a)->activating,
180 ((struct unit_times *)b)->activating);
181 }
182
183 static void free_unit_times(struct unit_times *t, unsigned n) {
184 struct unit_times *p;
185
186 for (p = t; p < t + n; p++)
187 free(p->name);
188
189 free(t);
190 }
191
192 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
193 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
194 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
195 int r, c = 0;
196 struct unit_times *unit_times = NULL;
197 size_t size = 0;
198 UnitInfo u;
199
200 r = sd_bus_call_method(
201 bus,
202 "org.freedesktop.systemd1",
203 "/org/freedesktop/systemd1",
204 "org.freedesktop.systemd1.Manager",
205 "ListUnits",
206 &error, &reply,
207 NULL);
208 if (r < 0) {
209 log_error("Failed to list units: %s", bus_error_message(&error, -r));
210 goto fail;
211 }
212
213 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
214 if (r < 0) {
215 bus_log_parse_error(r);
216 goto fail;
217 }
218
219 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
220 struct unit_times *t;
221
222 if (!GREEDY_REALLOC(unit_times, size, c+1)) {
223 r = log_oom();
224 goto fail;
225 }
226
227 t = unit_times+c;
228 t->name = NULL;
229
230 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
231
232 if (bus_get_uint64_property(bus, u.unit_path,
233 "org.freedesktop.systemd1.Unit",
234 "InactiveExitTimestampMonotonic",
235 &t->activating) < 0 ||
236 bus_get_uint64_property(bus, u.unit_path,
237 "org.freedesktop.systemd1.Unit",
238 "ActiveEnterTimestampMonotonic",
239 &t->activated) < 0 ||
240 bus_get_uint64_property(bus, u.unit_path,
241 "org.freedesktop.systemd1.Unit",
242 "ActiveExitTimestampMonotonic",
243 &t->deactivating) < 0 ||
244 bus_get_uint64_property(bus, u.unit_path,
245 "org.freedesktop.systemd1.Unit",
246 "InactiveEnterTimestampMonotonic",
247 &t->deactivated) < 0) {
248 r = -EIO;
249 goto fail;
250 }
251
252 if (t->activated >= t->activating)
253 t->time = t->activated - t->activating;
254 else if (t->deactivated >= t->activating)
255 t->time = t->deactivated - t->activating;
256 else
257 t->time = 0;
258
259 if (t->activating == 0)
260 continue;
261
262 t->name = strdup(u.id);
263 if (t->name == NULL) {
264 r = log_oom();
265 goto fail;
266 }
267 c++;
268 }
269 if (r < 0) {
270 bus_log_parse_error(r);
271 goto fail;
272 }
273
274 *out = unit_times;
275 return c;
276
277 fail:
278 free_unit_times(unit_times, (unsigned) c);
279 return r;
280 }
281
282 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
283 static struct boot_times times;
284 static bool cached = false;
285
286 if (cached)
287 goto finish;
288
289 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
290
291 if (bus_get_uint64_property(bus,
292 "/org/freedesktop/systemd1",
293 "org.freedesktop.systemd1.Manager",
294 "FirmwareTimestampMonotonic",
295 &times.firmware_time) < 0 ||
296 bus_get_uint64_property(bus,
297 "/org/freedesktop/systemd1",
298 "org.freedesktop.systemd1.Manager",
299 "LoaderTimestampMonotonic",
300 &times.loader_time) < 0 ||
301 bus_get_uint64_property(bus,
302 "/org/freedesktop/systemd1",
303 "org.freedesktop.systemd1.Manager",
304 "KernelTimestamp",
305 &times.kernel_time) < 0 ||
306 bus_get_uint64_property(bus,
307 "/org/freedesktop/systemd1",
308 "org.freedesktop.systemd1.Manager",
309 "InitRDTimestampMonotonic",
310 &times.initrd_time) < 0 ||
311 bus_get_uint64_property(bus,
312 "/org/freedesktop/systemd1",
313 "org.freedesktop.systemd1.Manager",
314 "UserspaceTimestampMonotonic",
315 &times.userspace_time) < 0 ||
316 bus_get_uint64_property(bus,
317 "/org/freedesktop/systemd1",
318 "org.freedesktop.systemd1.Manager",
319 "FinishTimestampMonotonic",
320 &times.finish_time) < 0 ||
321 bus_get_uint64_property(bus,
322 "/org/freedesktop/systemd1",
323 "org.freedesktop.systemd1.Manager",
324 "SecurityStartTimestampMonotonic",
325 &times.security_start_time) < 0 ||
326 bus_get_uint64_property(bus,
327 "/org/freedesktop/systemd1",
328 "org.freedesktop.systemd1.Manager",
329 "SecurityFinishTimestampMonotonic",
330 &times.security_finish_time) < 0 ||
331 bus_get_uint64_property(bus,
332 "/org/freedesktop/systemd1",
333 "org.freedesktop.systemd1.Manager",
334 "GeneratorsStartTimestampMonotonic",
335 &times.generators_start_time) < 0 ||
336 bus_get_uint64_property(bus,
337 "/org/freedesktop/systemd1",
338 "org.freedesktop.systemd1.Manager",
339 "GeneratorsFinishTimestampMonotonic",
340 &times.generators_finish_time) < 0 ||
341 bus_get_uint64_property(bus,
342 "/org/freedesktop/systemd1",
343 "org.freedesktop.systemd1.Manager",
344 "UnitsLoadStartTimestampMonotonic",
345 &times.unitsload_start_time) < 0 ||
346 bus_get_uint64_property(bus,
347 "/org/freedesktop/systemd1",
348 "org.freedesktop.systemd1.Manager",
349 "UnitsLoadFinishTimestampMonotonic",
350 &times.unitsload_finish_time) < 0)
351 return -EIO;
352
353 if (times.finish_time <= 0) {
354 log_error("Bootup is not yet finished. Please try again later.");
355 return -EINPROGRESS;
356 }
357
358 if (times.initrd_time)
359 times.kernel_done_time = times.initrd_time;
360 else
361 times.kernel_done_time = times.userspace_time;
362
363 cached = true;
364
365 finish:
366 *bt = &times;
367 return 0;
368 }
369
370 static void free_host_info(struct host_info *hi) {
371 free(hi->hostname);
372 free(hi->kernel_name);
373 free(hi->kernel_release);
374 free(hi->kernel_version);
375 free(hi->os_pretty_name);
376 free(hi->virtualization);
377 free(hi->architecture);
378 free(hi);
379 }
380
381 static int acquire_host_info(sd_bus *bus, struct host_info **hi) {
382 int r;
383 struct host_info *host;
384
385 static const struct bus_properties_map hostname_map[] = {
386 { "Hostname", "s", NULL, offsetof(struct host_info, hostname) },
387 { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) },
388 { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) },
389 { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) },
390 { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) },
391 {}
392 };
393
394 static const struct bus_properties_map manager_map[] = {
395 { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) },
396 { "Architecture", "s", NULL, offsetof(struct host_info, architecture) },
397 {}
398 };
399
400 host = new0(struct host_info, 1);
401 if (!host)
402 return log_oom();
403
404 r = bus_map_all_properties(bus,
405 "org.freedesktop.hostname1",
406 "/org/freedesktop/hostname1",
407 hostname_map,
408 host);
409 if (r < 0)
410 goto fail;
411
412 r = bus_map_all_properties(bus,
413 "org.freedesktop.systemd1",
414 "/org/freedesktop/systemd1",
415 manager_map,
416 host);
417 if (r < 0)
418 goto fail;
419
420 *hi = host;
421 return 0;
422 fail:
423 free_host_info(host);
424 return r;
425 }
426
427 static int pretty_boot_time(sd_bus *bus, char **_buf) {
428 char ts[FORMAT_TIMESPAN_MAX];
429 struct boot_times *t;
430 static char buf[4096];
431 size_t size;
432 char *ptr;
433 int r;
434
435 r = acquire_boot_times(bus, &t);
436 if (r < 0)
437 return r;
438
439 ptr = buf;
440 size = sizeof(buf);
441
442 size = strpcpyf(&ptr, size, "Startup finished in ");
443 if (t->firmware_time)
444 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
445 if (t->loader_time)
446 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
447 if (t->kernel_time)
448 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
449 if (t->initrd_time > 0)
450 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
451
452 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
453 if (t->kernel_time > 0)
454 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
455 else
456 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
457
458 ptr = strdup(buf);
459 if (!ptr)
460 return log_oom();
461
462 *_buf = ptr;
463 return 0;
464 }
465
466 static void svg_graph_box(double height, double begin, double end) {
467 long long i;
468
469 /* outside box, fill */
470 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
471 SCALE_X * (end - begin), SCALE_Y * height);
472
473 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
474 /* lines for each second */
475 if (i % 5000000 == 0)
476 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
477 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
478 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
479 else if (i % 1000000 == 0)
480 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
481 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
482 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
483 else
484 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
485 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
486 }
487 }
488
489 static int analyze_plot(sd_bus *bus) {
490 struct unit_times *times;
491 struct boot_times *boot;
492 struct host_info *host = NULL;
493 int n, m = 1, y=0;
494 double width;
495 _cleanup_free_ char *pretty_times = NULL;
496 struct unit_times *u;
497
498 n = acquire_boot_times(bus, &boot);
499 if (n < 0)
500 return n;
501
502 n = pretty_boot_time(bus, &pretty_times);
503 if (n < 0)
504 return n;
505
506 n = acquire_host_info(bus, &host);
507 if (n < 0)
508 return n;
509
510 n = acquire_time_data(bus, &times);
511 if (n <= 0)
512 goto out;
513
514 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
515
516 width = SCALE_X * (boot->firmware_time + boot->finish_time);
517 if (width < 800.0)
518 width = 800.0;
519
520 if (boot->firmware_time > boot->loader_time)
521 m++;
522 if (boot->loader_time) {
523 m++;
524 if (width < 1000.0)
525 width = 1000.0;
526 }
527 if (boot->initrd_time)
528 m++;
529 if (boot->kernel_time)
530 m++;
531
532 for (u = times; u < times + n; u++) {
533 double text_start, text_width;
534
535 if (u->activating < boot->userspace_time ||
536 u->activating > boot->finish_time) {
537 free(u->name);
538 u->name = NULL;
539 continue;
540 }
541
542 /* If the text cannot fit on the left side then
543 * increase the svg width so it fits on the right.
544 * TODO: calculate the text width more accurately */
545 text_width = 8.0 * strlen(u->name);
546 text_start = (boot->firmware_time + u->activating) * SCALE_X;
547 if (text_width > text_start && text_width + text_start > width)
548 width = text_width + text_start;
549
550 if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
551 && u->activated == 0 && u->deactivating == 0)
552 u->activated = u->deactivating = u->deactivated;
553 if (u->activated < u->activating || u->activated > boot->finish_time)
554 u->activated = boot->finish_time;
555 if (u->deactivating < u->activated || u->activated > boot->finish_time)
556 u->deactivating = boot->finish_time;
557 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
558 u->deactivated = boot->finish_time;
559 m++;
560 }
561
562 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
563 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
564 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
565
566 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
567 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
568 80.0 + width, 150.0 + (m * SCALE_Y) +
569 5 * SCALE_Y /* legend */);
570
571 /* write some basic info as a comment, including some help */
572 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
573 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
574 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
575 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
576 "<!-- point your browser to this file. -->\n\n"
577 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
578
579 /* style sheet */
580 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
581 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
582 " rect.background { fill: rgb(255,255,255); }\n"
583 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
584 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
585 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
586 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
587 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
588 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
589 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
590 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
591 " rect.security { fill: rgb(144,238,144); fill-opacity: 0.7; }\n"
592 " rect.generators { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
593 " rect.unitsload { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
594 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
595 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
596 "// line.sec1 { }\n"
597 " line.sec5 { stroke-width: 2; }\n"
598 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
599 " text { font-family: Verdana, Helvetica; font-size: 14px; }\n"
600 " text.left { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
601 " text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
602 " text.sec { font-size: 10px; }\n"
603 " ]]>\n </style>\n</defs>\n\n");
604
605 svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
606 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
607 svg("<text x=\"20\" y=\"30\">%s %s (%s %s %s) %s %s</text>",
608 isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name,
609 isempty(host->hostname) ? "" : host->hostname,
610 isempty(host->kernel_name) ? "" : host->kernel_name,
611 isempty(host->kernel_release) ? "" : host->kernel_release,
612 isempty(host->kernel_version) ? "" : host->kernel_version,
613 isempty(host->architecture) ? "" : host->architecture,
614 isempty(host->virtualization) ? "" : host->virtualization);
615
616 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
617 svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time);
618
619 if (boot->firmware_time) {
620 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
621 svg_text(true, -(double) boot->firmware_time, y, "firmware");
622 y++;
623 }
624 if (boot->loader_time) {
625 svg_bar("loader", -(double) boot->loader_time, 0, y);
626 svg_text(true, -(double) boot->loader_time, y, "loader");
627 y++;
628 }
629 if (boot->kernel_time) {
630 svg_bar("kernel", 0, boot->kernel_done_time, y);
631 svg_text(true, 0, y, "kernel");
632 y++;
633 }
634 if (boot->initrd_time) {
635 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
636 svg_text(true, boot->initrd_time, y, "initrd");
637 y++;
638 }
639 svg_bar("active", boot->userspace_time, boot->finish_time, y);
640 svg_bar("security", boot->security_start_time, boot->security_finish_time, y);
641 svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
642 svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
643 svg_text(true, boot->userspace_time, y, "systemd");
644 y++;
645
646 for (u = times; u < times + n; u++) {
647 char ts[FORMAT_TIMESPAN_MAX];
648 bool b;
649
650 if (!u->name)
651 continue;
652
653 svg_bar("activating", u->activating, u->activated, y);
654 svg_bar("active", u->activated, u->deactivating, y);
655 svg_bar("deactivating", u->deactivating, u->deactivated, y);
656
657 /* place the text on the left if we have passed the half of the svg width */
658 b = u->activating * SCALE_X < width / 2;
659 if (u->time)
660 svg_text(b, u->activating, y, "%s (%s)",
661 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
662 else
663 svg_text(b, u->activating, y, "%s", u->name);
664 y++;
665 }
666
667 svg("</g>\n");
668
669 /* Legend */
670 svg("<g transform=\"translate(20,100)\">\n");
671 y++;
672 svg_bar("activating", 0, 300000, y);
673 svg_text(true, 400000, y, "Activating");
674 y++;
675 svg_bar("active", 0, 300000, y);
676 svg_text(true, 400000, y, "Active");
677 y++;
678 svg_bar("deactivating", 0, 300000, y);
679 svg_text(true, 400000, y, "Deactivating");
680 y++;
681 svg_bar("security", 0, 300000, y);
682 svg_text(true, 400000, y, "Setting up security module");
683 y++;
684 svg_bar("generators", 0, 300000, y);
685 svg_text(true, 400000, y, "Generators");
686 y++;
687 svg_bar("unitsload", 0, 300000, y);
688 svg_text(true, 400000, y, "Loading unit files");
689 y++;
690
691 svg("</g>\n\n");
692
693 svg("</svg>\n");
694
695 free_unit_times(times, (unsigned) n);
696
697 n = 0;
698 out:
699 free_host_info(host);
700 return n;
701 }
702
703 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
704 bool last, struct unit_times *times, struct boot_times *boot) {
705 unsigned int i;
706 char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
707
708 for (i = level; i != 0; i--)
709 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE));
710
711 printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
712
713 if (times) {
714 if (times->time)
715 printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
716 format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
717 format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
718 else if (times->activated > boot->userspace_time)
719 printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
720 else
721 printf("%s", name);
722 } else
723 printf("%s", name);
724 printf("\n");
725
726 return 0;
727 }
728
729 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
730 _cleanup_free_ char *path = NULL;
731
732 assert(bus);
733 assert(name);
734 assert(deps);
735
736 path = unit_dbus_path_from_name(name);
737 if (path == NULL)
738 return -ENOMEM;
739
740 return bus_get_unit_property_strv(bus, path, "After", deps);
741 }
742
743 static Hashmap *unit_times_hashmap;
744
745 static int list_dependencies_compare(const void *_a, const void *_b) {
746 const char **a = (const char**) _a, **b = (const char**) _b;
747 usec_t usa = 0, usb = 0;
748 struct unit_times *times;
749
750 times = hashmap_get(unit_times_hashmap, *a);
751 if (times)
752 usa = times->activated;
753 times = hashmap_get(unit_times_hashmap, *b);
754 if (times)
755 usb = times->activated;
756
757 return usb - usa;
758 }
759
760 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
761 unsigned int branches) {
762 _cleanup_strv_free_ char **deps = NULL;
763 char **c;
764 int r = 0;
765 usec_t service_longest = 0;
766 int to_print = 0;
767 struct unit_times *times;
768 struct boot_times *boot;
769
770 if (strv_extend(units, name))
771 return log_oom();
772
773 r = list_dependencies_get_dependencies(bus, name, &deps);
774 if (r < 0)
775 return r;
776
777 qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
778
779 r = acquire_boot_times(bus, &boot);
780 if (r < 0)
781 return r;
782
783 STRV_FOREACH(c, deps) {
784 times = hashmap_get(unit_times_hashmap, *c);
785 if (times
786 && times->activated
787 && times->activated <= boot->finish_time
788 && (times->activated >= service_longest
789 || service_longest == 0)) {
790 service_longest = times->activated;
791 break;
792 }
793 }
794
795 if (service_longest == 0 )
796 return r;
797
798 STRV_FOREACH(c, deps) {
799 times = hashmap_get(unit_times_hashmap, *c);
800 if (times && times->activated
801 && times->activated <= boot->finish_time
802 && (service_longest - times->activated) <= arg_fuzz) {
803 to_print++;
804 }
805 }
806
807 if (!to_print)
808 return r;
809
810 STRV_FOREACH(c, deps) {
811 times = hashmap_get(unit_times_hashmap, *c);
812 if (!times
813 || !times->activated
814 || times->activated > boot->finish_time
815 || service_longest - times->activated > arg_fuzz)
816 continue;
817
818 to_print--;
819
820 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
821 if (r < 0)
822 return r;
823
824 if (strv_contains(*units, *c)) {
825 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
826 true, NULL, boot);
827 if (r < 0)
828 return r;
829 continue;
830 }
831
832 r = list_dependencies_one(bus, *c, level + 1, units,
833 (branches << 1) | (to_print ? 1 : 0));
834 if (r < 0)
835 return r;
836
837 if (!to_print)
838 break;
839 }
840 return 0;
841 }
842
843 static int list_dependencies(sd_bus *bus, const char *name) {
844 _cleanup_strv_free_ char **units = NULL;
845 char ts[FORMAT_TIMESPAN_MAX];
846 struct unit_times *times;
847 int r;
848 const char *path, *id;
849 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
850 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
851 struct boot_times *boot;
852
853 assert(bus);
854
855 path = unit_dbus_path_from_name(name);
856 if (path == NULL)
857 return -ENOMEM;
858
859 r = sd_bus_get_property(
860 bus,
861 "org.freedesktop.systemd1",
862 path,
863 "org.freedesktop.systemd1.Unit",
864 "Id",
865 &error,
866 &reply,
867 "s");
868 if (r < 0) {
869 log_error("Failed to get ID: %s", bus_error_message(&error, -r));
870 return r;
871 }
872
873 r = sd_bus_message_read(reply, "s", &id);
874 if (r < 0)
875 return bus_log_parse_error(r);
876
877 times = hashmap_get(unit_times_hashmap, id);
878
879 r = acquire_boot_times(bus, &boot);
880 if (r < 0)
881 return r;
882
883 if (times) {
884 if (times->time)
885 printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
886 format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
887 else if (times->activated > boot->userspace_time)
888 printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
889 else
890 printf("%s\n", id);
891 }
892
893 return list_dependencies_one(bus, name, 0, &units, 0);
894 }
895
896 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
897 struct unit_times *times;
898 unsigned int i;
899 Hashmap *h;
900 int n, r;
901
902 n = acquire_time_data(bus, &times);
903 if (n <= 0)
904 return n;
905
906 h = hashmap_new(string_hash_func, string_compare_func);
907 if (!h)
908 return -ENOMEM;
909
910 for (i = 0; i < (unsigned)n; i++) {
911 r = hashmap_put(h, times[i].name, &times[i]);
912 if (r < 0)
913 return r;
914 }
915 unit_times_hashmap = h;
916
917 pager_open_if_enabled();
918
919 puts("The time after the unit is active or started is printed after the \"@\" character.\n"
920 "The time the unit takes to start is printed after the \"+\" character.\n");
921
922 if (!strv_isempty(names)) {
923 char **name;
924 STRV_FOREACH(name, names)
925 list_dependencies(bus, *name);
926 } else
927 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
928
929 hashmap_free(h);
930 free_unit_times(times, (unsigned) n);
931 return 0;
932 }
933
934 static int analyze_blame(sd_bus *bus) {
935 struct unit_times *times;
936 unsigned i;
937 int n;
938
939 n = acquire_time_data(bus, &times);
940 if (n <= 0)
941 return n;
942
943 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
944
945 pager_open_if_enabled();
946
947 for (i = 0; i < (unsigned) n; i++) {
948 char ts[FORMAT_TIMESPAN_MAX];
949
950 if (times[i].time > 0)
951 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
952 }
953
954 free_unit_times(times, (unsigned) n);
955 return 0;
956 }
957
958 static int analyze_time(sd_bus *bus) {
959 _cleanup_free_ char *buf = NULL;
960 int r;
961
962 r = pretty_boot_time(bus, &buf);
963 if (r < 0)
964 return r;
965
966 puts(buf);
967 return 0;
968 }
969
970 static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[]) {
971 _cleanup_strv_free_ char **units = NULL;
972 char **unit;
973 int r;
974
975 assert(u);
976 assert(prop);
977 assert(color);
978
979 r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
980 if (r < 0)
981 return r;
982
983 STRV_FOREACH(unit, units) {
984 char **p;
985 bool match_found;
986
987 if (!strv_isempty(arg_dot_from_patterns)) {
988 match_found = false;
989
990 STRV_FOREACH(p, arg_dot_from_patterns)
991 if (fnmatch(*p, u->id, 0) == 0) {
992 match_found = true;
993 break;
994 }
995
996 if (!match_found)
997 continue;
998 }
999
1000 if (!strv_isempty(arg_dot_to_patterns)) {
1001 match_found = false;
1002
1003 STRV_FOREACH(p, arg_dot_to_patterns)
1004 if (fnmatch(*p, *unit, 0) == 0) {
1005 match_found = true;
1006 break;
1007 }
1008
1009 if (!match_found)
1010 continue;
1011 }
1012
1013 if (!strv_isempty(patterns)) {
1014 match_found = false;
1015
1016 STRV_FOREACH(p, patterns)
1017 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
1018 match_found = true;
1019 break;
1020 }
1021 if (!match_found)
1022 continue;
1023 }
1024
1025 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
1026 }
1027
1028 return 0;
1029 }
1030
1031 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[]) {
1032 int r;
1033
1034 assert(bus);
1035 assert(u);
1036
1037 if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
1038 r = graph_one_property(bus, u, "After", "green", patterns);
1039 if (r < 0)
1040 return r;
1041 }
1042
1043 if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
1044 r = graph_one_property(bus, u, "Requires", "black", patterns);
1045 if (r < 0)
1046 return r;
1047 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
1048 if (r < 0)
1049 return r;
1050 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
1051 if (r < 0)
1052 return r;
1053 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
1054 if (r < 0)
1055 return r;
1056 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
1057 if (r < 0)
1058 return r;
1059 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1060 if (r < 0)
1061 return r;
1062 }
1063
1064 return 0;
1065 }
1066
1067 static int dot(sd_bus *bus, char* patterns[]) {
1068 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1069 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1070 int r;
1071 UnitInfo u;
1072
1073 r = sd_bus_call_method(
1074 bus,
1075 "org.freedesktop.systemd1",
1076 "/org/freedesktop/systemd1",
1077 "org.freedesktop.systemd1.Manager",
1078 "ListUnits",
1079 &error,
1080 &reply,
1081 "");
1082 if (r < 0) {
1083 log_error("Failed to list units: %s", bus_error_message(&error, -r));
1084 return r;
1085 }
1086
1087 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1088 if (r < 0)
1089 return bus_log_parse_error(r);
1090
1091 printf("digraph systemd {\n");
1092
1093 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1094
1095 r = graph_one(bus, &u, patterns);
1096 if (r < 0)
1097 return r;
1098 }
1099 if (r < 0)
1100 return bus_log_parse_error(r);
1101
1102 printf("}\n");
1103
1104 log_info(" Color legend: black = Requires\n"
1105 " dark blue = Requisite\n"
1106 " dark grey = Wants\n"
1107 " red = Conflicts\n"
1108 " green = After\n");
1109
1110 if (on_tty())
1111 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1112 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1113
1114 return 0;
1115 }
1116
1117 static int dump(sd_bus *bus, char **args) {
1118 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1119 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1120 const char *text = NULL;
1121 int r;
1122
1123 if (!strv_isempty(args)) {
1124 log_error("Too many arguments.");
1125 return -E2BIG;
1126 }
1127
1128 pager_open_if_enabled();
1129
1130 r = sd_bus_call_method(
1131 bus,
1132 "org.freedesktop.systemd1",
1133 "/org/freedesktop/systemd1",
1134 "org.freedesktop.systemd1.Manager",
1135 "Dump",
1136 &error,
1137 &reply,
1138 "");
1139 if (r < 0) {
1140 log_error("Failed issue method call: %s", bus_error_message(&error, -r));
1141 return r;
1142 }
1143
1144 r = sd_bus_message_read(reply, "s", &text);
1145 if (r < 0)
1146 return bus_log_parse_error(r);
1147
1148 fputs(text, stdout);
1149 return 0;
1150 }
1151
1152 static int set_log_level(sd_bus *bus, char **args) {
1153 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1154 int r;
1155
1156 assert(bus);
1157 assert(args);
1158
1159 if (strv_length(args) != 1) {
1160 log_error("This command expects one argument only.");
1161 return -E2BIG;
1162 }
1163
1164 r = sd_bus_set_property(
1165 bus,
1166 "org.freedesktop.systemd1",
1167 "/org/freedesktop/systemd1",
1168 "org.freedesktop.systemd1.Manager",
1169 "LogLevel",
1170 &error,
1171 "s",
1172 args[0]);
1173 if (r < 0) {
1174 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1175 return -EIO;
1176 }
1177
1178 return 0;
1179 }
1180
1181 static int help(void) {
1182
1183 pager_open_if_enabled();
1184
1185 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1186 "Process systemd profiling information.\n\n"
1187 " -h --help Show this help\n"
1188 " --version Show package version\n"
1189 " --no-pager Do not pipe output into a pager\n"
1190 " --system Connect to system manager\n"
1191 " --user Connect to user manager\n"
1192 " -H --host=[USER@]HOST Operate on remote host\n"
1193 " -M --machine=CONTAINER Operate on local container\n"
1194 " --order When generating a dependency graph, show only order\n"
1195 " --require When generating a dependency graph, show only requirement\n"
1196 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1197 " When generating a dependency graph, filter only origins\n"
1198 " or destinations, respectively\n"
1199 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1200 " services, which finished TIMESPAN earlier, than the\n"
1201 " latest in the branch. The unit of TIMESPAN is seconds\n"
1202 " unless specified with a different unit, i.e. 50ms\n\n"
1203 "Commands:\n"
1204 " time Print time spent in the kernel before reaching userspace\n"
1205 " blame Print list of running units ordered by time to init\n"
1206 " critical-chain Print a tree of the time critical chain of units\n"
1207 " plot Output SVG graphic showing service initialization\n"
1208 " dot Output dependency graph in dot(1) format\n"
1209 " set-log-level LEVEL Set logging threshold for systemd\n"
1210 " dump Output state serialization of service manager\n",
1211 program_invocation_short_name);
1212
1213 /* When updating this list, including descriptions, apply
1214 * changes to shell-completion/bash/systemd and
1215 * shell-completion/systemd-zsh-completion.zsh too. */
1216
1217 return 0;
1218 }
1219
1220 static int parse_argv(int argc, char *argv[]) {
1221 enum {
1222 ARG_VERSION = 0x100,
1223 ARG_ORDER,
1224 ARG_REQUIRE,
1225 ARG_USER,
1226 ARG_SYSTEM,
1227 ARG_DOT_FROM_PATTERN,
1228 ARG_DOT_TO_PATTERN,
1229 ARG_FUZZ,
1230 ARG_NO_PAGER
1231 };
1232
1233 static const struct option options[] = {
1234 { "help", no_argument, NULL, 'h' },
1235 { "version", no_argument, NULL, ARG_VERSION },
1236 { "order", no_argument, NULL, ARG_ORDER },
1237 { "require", no_argument, NULL, ARG_REQUIRE },
1238 { "user", no_argument, NULL, ARG_USER },
1239 { "system", no_argument, NULL, ARG_SYSTEM },
1240 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1241 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1242 { "fuzz", required_argument, NULL, ARG_FUZZ },
1243 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1244 { "host", required_argument, NULL, 'H' },
1245 { "machine", required_argument, NULL, 'M' },
1246 {}
1247 };
1248
1249 int r, c;
1250
1251 assert(argc >= 0);
1252 assert(argv);
1253
1254 while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1255
1256 switch (c) {
1257
1258 case 'h':
1259 return help();
1260
1261 case ARG_VERSION:
1262 puts(PACKAGE_STRING);
1263 puts(SYSTEMD_FEATURES);
1264 return 0;
1265
1266 case ARG_USER:
1267 arg_user = true;
1268 break;
1269
1270 case ARG_SYSTEM:
1271 arg_user = false;
1272 break;
1273
1274 case ARG_ORDER:
1275 arg_dot = DEP_ORDER;
1276 break;
1277
1278 case ARG_REQUIRE:
1279 arg_dot = DEP_REQUIRE;
1280 break;
1281
1282 case ARG_DOT_FROM_PATTERN:
1283 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1284 return log_oom();
1285
1286 break;
1287
1288 case ARG_DOT_TO_PATTERN:
1289 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1290 return log_oom();
1291
1292 break;
1293
1294 case ARG_FUZZ:
1295 r = parse_sec(optarg, &arg_fuzz);
1296 if (r < 0)
1297 return r;
1298 break;
1299
1300 case ARG_NO_PAGER:
1301 arg_no_pager = true;
1302 break;
1303
1304 case 'H':
1305 arg_transport = BUS_TRANSPORT_REMOTE;
1306 arg_host = optarg;
1307 break;
1308
1309 case 'M':
1310 arg_transport = BUS_TRANSPORT_CONTAINER;
1311 arg_host = optarg;
1312 break;
1313
1314 case '?':
1315 return -EINVAL;
1316
1317 default:
1318 assert_not_reached("Unhandled option");
1319 }
1320 }
1321
1322 return 1;
1323 }
1324
1325 int main(int argc, char *argv[]) {
1326 _cleanup_bus_unref_ sd_bus *bus = NULL;
1327 int r;
1328
1329 setlocale(LC_ALL, "");
1330 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1331 log_parse_environment();
1332 log_open();
1333
1334 r = parse_argv(argc, argv);
1335 if (r <= 0)
1336 goto finish;
1337
1338 r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1339 if (r < 0) {
1340 log_error("Failed to create bus connection: %s", strerror(-r));
1341 goto finish;
1342 }
1343
1344 if (!argv[optind] || streq(argv[optind], "time"))
1345 r = analyze_time(bus);
1346 else if (streq(argv[optind], "blame"))
1347 r = analyze_blame(bus);
1348 else if (streq(argv[optind], "critical-chain"))
1349 r = analyze_critical_chain(bus, argv+optind+1);
1350 else if (streq(argv[optind], "plot"))
1351 r = analyze_plot(bus);
1352 else if (streq(argv[optind], "dot"))
1353 r = dot(bus, argv+optind+1);
1354 else if (streq(argv[optind], "dump"))
1355 r = dump(bus, argv+optind+1);
1356 else if (streq(argv[optind], "set-log-level"))
1357 r = set_log_level(bus, argv+optind+1);
1358 else
1359 log_error("Unknown operation '%s'.", argv[optind]);
1360
1361 finish:
1362 pager_close();
1363
1364 strv_free(arg_dot_from_patterns);
1365 strv_free(arg_dot_to_patterns);
1366
1367 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1368 }