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