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