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