chiark / gitweb /
zsh-completion: add s-a critical-chain
[elogind.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 }