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