]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/systemd-analyze.c
util: make time formatting a bit smarter
[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
29 #include "install.h"
30 #include "log.h"
31 #include "dbus-common.h"
32 #include "build.h"
33 #include "util.h"
34 #include "strxcpyx.h"
35 #include "fileio.h"
36
37 #define SCALE_X (0.1 / 1000.0) /* pixels per us */
38 #define SCALE_Y 20.0
39
40 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
41
42 #define svg(...) printf(__VA_ARGS__)
43
44 #define svg_bar(class, x1, x2, y) \
45 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
46 (class), \
47 SCALE_X * (x1), SCALE_Y * (y), \
48 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
49
50 #define svg_text(b, x, y, format, ...) \
51 do { \
52 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
53 svg(format, ## __VA_ARGS__); \
54 svg("</text>\n"); \
55 } while(false)
56
57 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
58 static enum dot {
59 DEP_ALL,
60 DEP_ORDER,
61 DEP_REQUIRE
62 } arg_dot = DEP_ALL;
63
64 struct boot_times {
65 usec_t firmware_time;
66 usec_t loader_time;
67 usec_t kernel_time;
68 usec_t kernel_done_time;
69 usec_t initrd_time;
70 usec_t userspace_time;
71 usec_t finish_time;
72 };
73 struct unit_times {
74 char *name;
75 usec_t ixt;
76 usec_t iet;
77 usec_t axt;
78 usec_t aet;
79 usec_t time;
80 };
81
82 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
83 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
84 DBusMessageIter iter, sub;
85 int r;
86
87 r = bus_method_call_with_reply(
88 bus,
89 "org.freedesktop.systemd1",
90 path,
91 "org.freedesktop.DBus.Properties",
92 "Get",
93 &reply,
94 NULL,
95 DBUS_TYPE_STRING, &interface,
96 DBUS_TYPE_STRING, &property,
97 DBUS_TYPE_INVALID);
98 if (r < 0)
99 return r;
100
101 if (!dbus_message_iter_init(reply, &iter) ||
102 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
103 log_error("Failed to parse reply.");
104 return -EIO;
105 }
106
107 dbus_message_iter_recurse(&iter, &sub);
108
109 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
110 log_error("Failed to parse reply.");
111 return -EIO;
112 }
113
114 dbus_message_iter_get_basic(&sub, val);
115
116 return 0;
117 }
118
119 static int compare_unit_time(const void *a, const void *b) {
120 return compare(((struct unit_times *)b)->time,
121 ((struct unit_times *)a)->time);
122 }
123
124 static int compare_unit_start(const void *a, const void *b) {
125 return compare(((struct unit_times *)a)->ixt,
126 ((struct unit_times *)b)->ixt);
127 }
128
129 static int get_os_name(char **_n) {
130 char *n = NULL;
131 int r;
132
133 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
134 if (r < 0)
135 return r;
136
137 if (!n)
138 return -ENOENT;
139
140 *_n = n;
141 return 0;
142 }
143
144 static void free_unit_times(struct unit_times *t, unsigned n) {
145 struct unit_times *p;
146
147 for (p = t; p < t + n; p++)
148 free(p->name);
149
150 free(t);
151 }
152
153 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
154 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
155 DBusMessageIter iter, sub;
156 int r, c = 0, n_units = 0;
157 struct unit_times *unit_times = NULL;
158
159 r = bus_method_call_with_reply(
160 bus,
161 "org.freedesktop.systemd1",
162 "/org/freedesktop/systemd1",
163 "org.freedesktop.systemd1.Manager",
164 "ListUnits",
165 &reply,
166 NULL,
167 DBUS_TYPE_INVALID);
168 if (r < 0)
169 goto fail;
170
171 if (!dbus_message_iter_init(reply, &iter) ||
172 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
173 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
174 log_error("Failed to parse reply.");
175 r = -EIO;
176 goto fail;
177 }
178
179 for (dbus_message_iter_recurse(&iter, &sub);
180 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
181 dbus_message_iter_next(&sub)) {
182 struct unit_info u;
183 struct unit_times *t;
184
185 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
186 log_error("Failed to parse reply.");
187 r = -EIO;
188 goto fail;
189 }
190
191 if (c >= n_units) {
192 struct unit_times *w;
193
194 n_units = MAX(2*c, 16);
195 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
196
197 if (!w) {
198 r = log_oom();
199 goto fail;
200 }
201
202 unit_times = w;
203 }
204 t = unit_times+c;
205 t->name = NULL;
206
207 r = bus_parse_unit_info(&sub, &u);
208 if (r < 0)
209 goto fail;
210
211 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
212
213 if (bus_get_uint64_property(bus, u.unit_path,
214 "org.freedesktop.systemd1.Unit",
215 "InactiveExitTimestampMonotonic",
216 &t->ixt) < 0 ||
217 bus_get_uint64_property(bus, u.unit_path,
218 "org.freedesktop.systemd1.Unit",
219 "ActiveEnterTimestampMonotonic",
220 &t->aet) < 0 ||
221 bus_get_uint64_property(bus, u.unit_path,
222 "org.freedesktop.systemd1.Unit",
223 "ActiveExitTimestampMonotonic",
224 &t->axt) < 0 ||
225 bus_get_uint64_property(bus, u.unit_path,
226 "org.freedesktop.systemd1.Unit",
227 "InactiveEnterTimestampMonotonic",
228 &t->iet) < 0) {
229 r = -EIO;
230 goto fail;
231 }
232
233 if (t->aet >= t->ixt)
234 t->time = t->aet - t->ixt;
235 else if (t->iet >= t->ixt)
236 t->time = t->iet - t->ixt;
237 else
238 t->time = 0;
239
240 if (t->ixt == 0)
241 continue;
242
243 t->name = strdup(u.id);
244 if (t->name == NULL) {
245 r = log_oom();
246 goto fail;
247 }
248 c++;
249 }
250
251 *out = unit_times;
252 return c;
253
254 fail:
255 free_unit_times(unit_times, (unsigned) c);
256 return r;
257 }
258
259 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
260 static struct boot_times times;
261 static bool cached = false;
262
263 if (cached)
264 goto finish;
265
266 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
267
268 if (bus_get_uint64_property(bus,
269 "/org/freedesktop/systemd1",
270 "org.freedesktop.systemd1.Manager",
271 "FirmwareTimestampMonotonic",
272 &times.firmware_time) < 0 ||
273 bus_get_uint64_property(bus,
274 "/org/freedesktop/systemd1",
275 "org.freedesktop.systemd1.Manager",
276 "LoaderTimestampMonotonic",
277 &times.loader_time) < 0 ||
278 bus_get_uint64_property(bus,
279 "/org/freedesktop/systemd1",
280 "org.freedesktop.systemd1.Manager",
281 "KernelTimestamp",
282 &times.kernel_time) < 0 ||
283 bus_get_uint64_property(bus,
284 "/org/freedesktop/systemd1",
285 "org.freedesktop.systemd1.Manager",
286 "InitRDTimestampMonotonic",
287 &times.initrd_time) < 0 ||
288 bus_get_uint64_property(bus,
289 "/org/freedesktop/systemd1",
290 "org.freedesktop.systemd1.Manager",
291 "UserspaceTimestampMonotonic",
292 &times.userspace_time) < 0 ||
293 bus_get_uint64_property(bus,
294 "/org/freedesktop/systemd1",
295 "org.freedesktop.systemd1.Manager",
296 "FinishTimestampMonotonic",
297 &times.finish_time) < 0)
298 return -EIO;
299
300 if (times.finish_time <= 0) {
301 log_error("Bootup is not yet finished. Please try again later.");
302 return -EAGAIN;
303 }
304
305 if (times.initrd_time)
306 times.kernel_done_time = times.initrd_time;
307 else
308 times.kernel_done_time = times.userspace_time;
309
310 cached = true;
311
312 finish:
313 *bt = &times;
314 return 0;
315 }
316
317 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
318 char ts[FORMAT_TIMESPAN_MAX];
319 struct boot_times *t;
320 static char buf[4096];
321 size_t size;
322 char *ptr;
323 int r;
324
325 r = acquire_boot_times(bus, &t);
326 if (r < 0)
327 return r;
328
329 ptr = buf;
330 size = sizeof(buf);
331
332 size = strpcpyf(&ptr, size, "Startup finished in ");
333 if (t->firmware_time)
334 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
335 if (t->loader_time)
336 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
337 if (t->kernel_time)
338 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
339 if (t->initrd_time > 0)
340 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
341
342 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
343 if (t->kernel_time > 0)
344 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
345 else
346 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
347
348 ptr = strdup(buf);
349 if (!ptr)
350 return log_oom();
351
352 *_buf = ptr;
353 return 0;
354 }
355
356 static void svg_graph_box(double height, double begin, double end) {
357 long long i;
358
359 /* outside box, fill */
360 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
361 SCALE_X * (end - begin), SCALE_Y * height);
362
363 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
364 /* lines for each second */
365 if (i % 5000000 == 0)
366 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
367 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
368 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
369 else if (i % 1000000 == 0)
370 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
371 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
372 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
373 else
374 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
375 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
376 }
377 }
378
379 static int analyze_plot(DBusConnection *bus) {
380 struct unit_times *times;
381 struct boot_times *boot;
382 struct utsname name;
383 int n, m = 1, y=0;
384 double width;
385 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
386 struct unit_times *u;
387
388 n = acquire_boot_times(bus, &boot);
389 if (n < 0)
390 return n;
391
392 n = pretty_boot_time(bus, &pretty_times);
393 if (n < 0)
394 return n;
395
396 get_os_name(&osname);
397 assert_se(uname(&name) >= 0);
398
399 n = acquire_time_data(bus, &times);
400 if (n <= 0)
401 return n;
402
403 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
404
405 width = SCALE_X * (boot->firmware_time + boot->finish_time);
406 if (width < 800.0)
407 width = 800.0;
408
409 if (boot->firmware_time > boot->loader_time)
410 m++;
411 if (boot->loader_time) {
412 m++;
413 if (width < 1000.0)
414 width = 1000.0;
415 }
416 if (boot->initrd_time)
417 m++;
418 if (boot->kernel_time)
419 m++;
420
421 for (u = times; u < times + n; u++) {
422 double len;
423
424 if (u->ixt < boot->userspace_time ||
425 u->ixt > boot->finish_time) {
426 free(u->name);
427 u->name = NULL;
428 continue;
429 }
430 len = ((boot->firmware_time + u->ixt) * SCALE_X)
431 + (10.0 * strlen(u->name));
432 if (len > width)
433 width = len;
434
435 if (u->iet > u->ixt && u->iet <= boot->finish_time
436 && u->aet == 0 && u->axt == 0)
437 u->aet = u->axt = u->iet;
438 if (u->aet < u->ixt || u->aet > boot->finish_time)
439 u->aet = boot->finish_time;
440 if (u->axt < u->aet || u->aet > boot->finish_time)
441 u->axt = boot->finish_time;
442 if (u->iet < u->axt || u->iet > boot->finish_time)
443 u->iet = boot->finish_time;
444 m++;
445 }
446
447 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
448 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
449 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
450
451 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
452 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
453 80.0 + width, 150.0 + (m * SCALE_Y));
454
455 /* write some basic info as a comment, including some help */
456 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
457 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
458 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
459 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
460 "<!-- point your browser to this file. -->\n\n"
461 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
462
463 /* style sheet */
464 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
465 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
466 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
467 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
468 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
469 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
470 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
471 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
472 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
473 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
474 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
475 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
476 "// line.sec1 { }\n"
477 " line.sec5 { stroke-width: 2; }\n"
478 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
479 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
480 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
481 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
482 " text.sec { font-size: 8; }\n"
483 " ]]>\n </style>\n</defs>\n\n");
484
485 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
486 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
487 isempty(osname) ? "Linux" : osname,
488 name.nodename, name.release, name.version, name.machine);
489 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
490 120.0 + (m *SCALE_Y));
491
492 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
493 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
494
495 if (boot->firmware_time) {
496 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
497 svg_text(true, -(double) boot->firmware_time, y, "firmware");
498 y++;
499 }
500 if (boot->loader_time) {
501 svg_bar("loader", -(double) boot->loader_time, 0, y);
502 svg_text(true, -(double) boot->loader_time, y, "loader");
503 y++;
504 }
505 if (boot->kernel_time) {
506 svg_bar("kernel", 0, boot->kernel_done_time, y);
507 svg_text(true, 0, y, "kernel");
508 y++;
509 }
510 if (boot->initrd_time) {
511 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
512 svg_text(true, boot->initrd_time, y, "initrd");
513 y++;
514 }
515 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
516 svg_text("left", boot->userspace_time, y, "userspace");
517 y++;
518
519 for (u = times; u < times + n; u++) {
520 char ts[FORMAT_TIMESPAN_MAX];
521 bool b;
522
523 if (!u->name)
524 continue;
525
526 svg_bar("activating", u->ixt, u->aet, y);
527 svg_bar("active", u->aet, u->axt, y);
528 svg_bar("deactivating", u->axt, u->iet, y);
529
530 b = u->ixt * SCALE_X > width * 2 / 3;
531 if (u->time)
532 svg_text(b, u->ixt, y, "%s (%s)",
533 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
534 else
535 svg_text(b, u->ixt, y, "%s", u->name);
536 y++;
537 }
538 svg("</g>\n\n");
539
540 svg("</svg>");
541
542 free_unit_times(times, (unsigned) n);
543
544 return 0;
545 }
546
547 static int analyze_blame(DBusConnection *bus) {
548 struct unit_times *times;
549 unsigned i;
550 int n;
551
552 n = acquire_time_data(bus, &times);
553 if (n <= 0)
554 return n;
555
556 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
557
558 for (i = 0; i < (unsigned) n; i++) {
559 char ts[FORMAT_TIMESPAN_MAX];
560
561 if (times[i].time > 0)
562 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
563 }
564
565 free_unit_times(times, (unsigned) n);
566 return 0;
567 }
568
569 static int analyze_time(DBusConnection *bus) {
570 _cleanup_free_ char *buf = NULL;
571 int r;
572
573 r = pretty_boot_time(bus, &buf);
574 if (r < 0)
575 return r;
576
577 puts(buf);
578 return 0;
579 }
580
581 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
582
583 static const char * const colors[] = {
584 "Requires", "[color=\"black\"]",
585 "RequiresOverridable", "[color=\"black\"]",
586 "Requisite", "[color=\"darkblue\"]",
587 "RequisiteOverridable", "[color=\"darkblue\"]",
588 "Wants", "[color=\"grey66\"]",
589 "Conflicts", "[color=\"red\"]",
590 "ConflictedBy", "[color=\"red\"]",
591 "After", "[color=\"green\"]"
592 };
593
594 const char *c = NULL;
595 unsigned i;
596
597 assert(name);
598 assert(prop);
599 assert(iter);
600
601 for (i = 0; i < ELEMENTSOF(colors); i += 2)
602 if (streq(colors[i], prop)) {
603 c = colors[i+1];
604 break;
605 }
606
607 if (!c)
608 return 0;
609
610 if (arg_dot != DEP_ALL)
611 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
612 return 0;
613
614 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
615 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
616 DBusMessageIter sub;
617
618 dbus_message_iter_recurse(iter, &sub);
619
620 for (dbus_message_iter_recurse(iter, &sub);
621 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
622 dbus_message_iter_next(&sub)) {
623 const char *s;
624
625 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
626 dbus_message_iter_get_basic(&sub, &s);
627 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
628 }
629 }
630
631 return 0;
632 }
633
634 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
635 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
636 const char *interface = "org.freedesktop.systemd1.Unit";
637 int r;
638 DBusMessageIter iter, sub, sub2, sub3;
639
640 assert(bus);
641 assert(u);
642
643 r = bus_method_call_with_reply(
644 bus,
645 "org.freedesktop.systemd1",
646 u->unit_path,
647 "org.freedesktop.DBus.Properties",
648 "GetAll",
649 &reply,
650 NULL,
651 DBUS_TYPE_STRING, &interface,
652 DBUS_TYPE_INVALID);
653 if (r < 0)
654 return r;
655
656 if (!dbus_message_iter_init(reply, &iter) ||
657 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
658 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
659 log_error("Failed to parse reply.");
660 return -EIO;
661 }
662
663 for (dbus_message_iter_recurse(&iter, &sub);
664 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
665 dbus_message_iter_next(&sub)) {
666 const char *prop;
667
668 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
669 dbus_message_iter_recurse(&sub, &sub2);
670
671 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
672 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
673 log_error("Failed to parse reply.");
674 return -EIO;
675 }
676
677 dbus_message_iter_recurse(&sub2, &sub3);
678 r = graph_one_property(u->id, prop, &sub3);
679 if (r < 0)
680 return r;
681 }
682
683 return 0;
684 }
685
686 static int dot(DBusConnection *bus) {
687 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
688 DBusMessageIter iter, sub;
689 int r;
690
691 r = bus_method_call_with_reply(
692 bus,
693 "org.freedesktop.systemd1",
694 "/org/freedesktop/systemd1",
695 "org.freedesktop.systemd1.Manager",
696 "ListUnits",
697 &reply,
698 NULL,
699 DBUS_TYPE_INVALID);
700 if (r < 0)
701 return r;
702
703 if (!dbus_message_iter_init(reply, &iter) ||
704 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
705 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
706 log_error("Failed to parse reply.");
707 return -EIO;
708 }
709
710 printf("digraph systemd {\n");
711
712 for (dbus_message_iter_recurse(&iter, &sub);
713 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
714 dbus_message_iter_next(&sub)) {
715 struct unit_info u;
716
717 r = bus_parse_unit_info(&sub, &u);
718 if (r < 0)
719 return -EIO;
720
721 r = graph_one(bus, &u);
722 if (r < 0)
723 return r;
724 }
725
726 printf("}\n");
727
728 log_info(" Color legend: black = Requires\n"
729 " dark blue = Requisite\n"
730 " dark grey = Wants\n"
731 " red = Conflicts\n"
732 " green = After\n");
733
734 if (on_tty())
735 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
736 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
737
738 return 0;
739 }
740
741 static void analyze_help(void)
742 {
743 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
744 "Process systemd profiling information\n\n"
745 " -h --help Show this help\n"
746 " --version Show package version\n"
747 " --system Connect to system manager\n"
748 " --user Connect to user service manager\n"
749 " --order When generating a dependency graph, show only order\n"
750 " --require When generating a dependency graph, show only requirement\n\n"
751 "Commands:\n"
752 " time Print time spent in the kernel before reaching userspace\n"
753 " blame Print list of running units ordered by time to init\n"
754 " plot Output SVG graphic showing service initialization\n"
755 " dot Dump dependency graph (in dot(1) format)\n\n",
756 program_invocation_short_name);
757 }
758
759 static int parse_argv(int argc, char *argv[])
760 {
761 enum {
762 ARG_VERSION = 0x100,
763 ARG_ORDER,
764 ARG_REQUIRE,
765 ARG_USER,
766 ARG_SYSTEM
767 };
768
769 static const struct option options[] = {
770 { "help", no_argument, NULL, 'h' },
771 { "version", no_argument, NULL, ARG_VERSION },
772 { "order", no_argument, NULL, ARG_ORDER },
773 { "require", no_argument, NULL, ARG_REQUIRE },
774 { "user", no_argument, NULL, ARG_USER },
775 { "system", no_argument, NULL, ARG_SYSTEM },
776 { NULL, 0, NULL, 0 }
777 };
778
779 assert(argc >= 0);
780 assert(argv);
781
782 for (;;) {
783 switch (getopt_long(argc, argv, "h", options, NULL)) {
784
785 case 'h':
786 analyze_help();
787 return 0;
788
789 case ARG_VERSION:
790 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
791 return 0;
792
793 case ARG_USER:
794 arg_scope = UNIT_FILE_USER;
795 break;
796
797 case ARG_SYSTEM:
798 arg_scope = UNIT_FILE_SYSTEM;
799 break;
800
801 case ARG_ORDER:
802 arg_dot = DEP_ORDER;
803 break;
804
805 case ARG_REQUIRE:
806 arg_dot = DEP_REQUIRE;
807 break;
808
809 case -1:
810 return 1;
811
812 case '?':
813 return -EINVAL;
814
815 default:
816 assert_not_reached("Unhandled option");
817 }
818 }
819 }
820
821 int main(int argc, char *argv[]) {
822 int r;
823 DBusConnection *bus = NULL;
824
825 setlocale(LC_ALL, "");
826 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
827 log_parse_environment();
828 log_open();
829
830 r = parse_argv(argc, argv);
831 if (r < 0)
832 return EXIT_FAILURE;
833 else if (r <= 0)
834 return EXIT_SUCCESS;
835
836 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
837 if (!bus)
838 return EXIT_FAILURE;
839
840 if (!argv[optind] || streq(argv[optind], "time"))
841 r = analyze_time(bus);
842 else if (streq(argv[optind], "blame"))
843 r = analyze_blame(bus);
844 else if (streq(argv[optind], "plot"))
845 r = analyze_plot(bus);
846 else if (streq(argv[optind], "dot"))
847 r = dot(bus);
848 else
849 log_error("Unknown operation '%s'.", argv[optind]);
850
851 dbus_connection_unref(bus);
852
853 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
854 }