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