chiark / gitweb /
029ce9c9ebd3b1ababca458fa8f86137c9042de9
[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
29 #include "install.h"
30 #include "log.h"
31 #include "dbus-common.h"
32 #include "build.h"
33 #include "util.h"
34 #include "strxcpyx.h"
35 #include "fileio.h"
36
37 #define SCALE_X (0.1 / 1000.0)   /* pixels per us */
38 #define SCALE_Y 20.0
39
40 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
41
42 #define svg(...) printf(__VA_ARGS__)
43
44 #define svg_bar(class, x1, x2, y)                                       \
45         svg("  <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
46             (class),                                                    \
47             SCALE_X * (x1), SCALE_Y * (y),                              \
48             SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
49
50 #define svg_text(b, x, y, format, ...)                                  \
51         do {                                                            \
52                 svg("  <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
53                 svg(format, ## __VA_ARGS__);                            \
54                 svg("</text>\n");                                       \
55         } while(false)
56
57 static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
58 static enum dot {
59         DEP_ALL,
60         DEP_ORDER,
61         DEP_REQUIRE
62 } arg_dot = DEP_ALL;
63
64 struct boot_times {
65         usec_t firmware_time;
66         usec_t loader_time;
67         usec_t kernel_time;
68         usec_t kernel_done_time;
69         usec_t initrd_time;
70         usec_t userspace_time;
71         usec_t finish_time;
72 };
73 struct unit_times {
74         char *name;
75         usec_t ixt;
76         usec_t iet;
77         usec_t axt;
78         usec_t aet;
79         usec_t time;
80 };
81
82 static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
83         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
84         DBusMessageIter iter, sub;
85         int r;
86
87         r = bus_method_call_with_reply(
88                         bus,
89                         "org.freedesktop.systemd1",
90                         path,
91                         "org.freedesktop.DBus.Properties",
92                         "Get",
93                         &reply,
94                         NULL,
95                         DBUS_TYPE_STRING, &interface,
96                         DBUS_TYPE_STRING, &property,
97                         DBUS_TYPE_INVALID);
98         if (r < 0)
99                 return r;
100
101         if (!dbus_message_iter_init(reply, &iter) ||
102             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)  {
103                 log_error("Failed to parse reply.");
104                 return -EIO;
105         }
106
107         dbus_message_iter_recurse(&iter, &sub);
108
109         if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64)  {
110                 log_error("Failed to parse reply.");
111                 return -EIO;
112         }
113
114         dbus_message_iter_get_basic(&sub, val);
115
116         return 0;
117 }
118
119 static int compare_unit_time(const void *a, const void *b) {
120         return compare(((struct unit_times *)b)->time,
121                        ((struct unit_times *)a)->time);
122 }
123
124 static int compare_unit_start(const void *a, const void *b) {
125         return compare(((struct unit_times *)a)->ixt,
126                        ((struct unit_times *)b)->ixt);
127 }
128
129 static int get_os_name(char **_n) {
130         char *n = NULL;
131         int r;
132
133         r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
134         if (r < 0)
135                 return r;
136
137         if (!n)
138                 return -ENOENT;
139
140         *_n = n;
141         return 0;
142 }
143
144 static void free_unit_times(struct unit_times *t, unsigned n) {
145         struct unit_times *p;
146
147         for (p = t; p < t + n; p++)
148                 free(p->name);
149
150         free(t);
151 }
152
153 static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
154         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
155         DBusMessageIter iter, sub;
156         int r, c = 0, n_units = 0;
157         struct unit_times *unit_times = NULL;
158
159         r = bus_method_call_with_reply(
160                         bus,
161                         "org.freedesktop.systemd1",
162                         "/org/freedesktop/systemd1",
163                         "org.freedesktop.systemd1.Manager",
164                         "ListUnits",
165                         &reply,
166                         NULL,
167                         DBUS_TYPE_INVALID);
168         if (r < 0)
169                 goto fail;
170
171         if (!dbus_message_iter_init(reply, &iter) ||
172                         dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
173                         dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
174                 log_error("Failed to parse reply.");
175                 r = -EIO;
176                 goto fail;
177         }
178
179         for (dbus_message_iter_recurse(&iter, &sub);
180              dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
181              dbus_message_iter_next(&sub)) {
182                 struct unit_info u;
183                 struct unit_times *t;
184
185                 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
186                         log_error("Failed to parse reply.");
187                         r = -EIO;
188                         goto fail;
189                 }
190
191                 if (c >= n_units) {
192                         struct unit_times *w;
193
194                         n_units = MAX(2*c, 16);
195                         w = realloc(unit_times, sizeof(struct unit_times) * n_units);
196
197                         if (!w) {
198                                 r = log_oom();
199                                 goto fail;
200                         }
201
202                         unit_times = w;
203                 }
204                 t = unit_times+c;
205                 t->name = NULL;
206
207                 r = bus_parse_unit_info(&sub, &u);
208                 if (r < 0)
209                         goto fail;
210
211                 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
212
213                 if (bus_get_uint64_property(bus, u.unit_path,
214                                             "org.freedesktop.systemd1.Unit",
215                                             "InactiveExitTimestampMonotonic",
216                                             &t->ixt) < 0 ||
217                     bus_get_uint64_property(bus, u.unit_path,
218                                             "org.freedesktop.systemd1.Unit",
219                                             "ActiveEnterTimestampMonotonic",
220                                             &t->aet) < 0 ||
221                     bus_get_uint64_property(bus, u.unit_path,
222                                             "org.freedesktop.systemd1.Unit",
223                                             "ActiveExitTimestampMonotonic",
224                                             &t->axt) < 0 ||
225                     bus_get_uint64_property(bus, u.unit_path,
226                                             "org.freedesktop.systemd1.Unit",
227                                             "InactiveEnterTimestampMonotonic",
228                                             &t->iet) < 0) {
229                         r = -EIO;
230                         goto fail;
231                 }
232
233                 if (t->aet >= t->ixt)
234                         t->time = t->aet - t->ixt;
235                 else if (t->iet >= t->ixt)
236                         t->time = t->iet - t->ixt;
237                 else
238                         t->time = 0;
239
240                 if (t->ixt == 0)
241                         continue;
242
243                 t->name = strdup(u.id);
244                 if (t->name == NULL) {
245                         r = log_oom();
246                         goto fail;
247                 }
248                 c++;
249         }
250
251         *out = unit_times;
252         return c;
253
254 fail:
255         free_unit_times(unit_times, (unsigned) c);
256         return r;
257 }
258
259 static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
260         static struct boot_times times;
261         static bool cached = false;
262
263         if (cached)
264                 goto finish;
265
266         assert_cc(sizeof(usec_t) == sizeof(uint64_t));
267
268         if (bus_get_uint64_property(bus,
269                                     "/org/freedesktop/systemd1",
270                                     "org.freedesktop.systemd1.Manager",
271                                     "FirmwareTimestampMonotonic",
272                                     &times.firmware_time) < 0 ||
273             bus_get_uint64_property(bus,
274                                     "/org/freedesktop/systemd1",
275                                     "org.freedesktop.systemd1.Manager",
276                                     "LoaderTimestampMonotonic",
277                                     &times.loader_time) < 0 ||
278             bus_get_uint64_property(bus,
279                                     "/org/freedesktop/systemd1",
280                                     "org.freedesktop.systemd1.Manager",
281                                     "KernelTimestamp",
282                                     &times.kernel_time) < 0 ||
283             bus_get_uint64_property(bus,
284                                     "/org/freedesktop/systemd1",
285                                     "org.freedesktop.systemd1.Manager",
286                                     "InitRDTimestampMonotonic",
287                                     &times.initrd_time) < 0 ||
288             bus_get_uint64_property(bus,
289                                     "/org/freedesktop/systemd1",
290                                     "org.freedesktop.systemd1.Manager",
291                                     "UserspaceTimestampMonotonic",
292                                     &times.userspace_time) < 0 ||
293             bus_get_uint64_property(bus,
294                                     "/org/freedesktop/systemd1",
295                                     "org.freedesktop.systemd1.Manager",
296                                     "FinishTimestampMonotonic",
297                                     &times.finish_time) < 0)
298                 return -EIO;
299
300         if (times.finish_time <= 0) {
301                 log_error("Bootup is not yet finished. Please try again later.");
302                 return -EAGAIN;
303         }
304
305         if (times.initrd_time)
306                 times.kernel_done_time = times.initrd_time;
307         else
308                 times.kernel_done_time = times.userspace_time;
309
310         cached = true;
311
312 finish:
313         *bt = &times;
314         return 0;
315 }
316
317 static int pretty_boot_time(DBusConnection *bus, char **_buf) {
318         char ts[FORMAT_TIMESPAN_MAX];
319         struct boot_times *t;
320         static char buf[4096];
321         size_t size;
322         char *ptr;
323         int r;
324
325         r = acquire_boot_times(bus, &t);
326         if (r < 0)
327                 return r;
328
329         ptr = buf;
330         size = sizeof(buf);
331
332         size = strpcpyf(&ptr, size, "Startup finished in ");
333         if (t->firmware_time)
334                 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
335         if (t->loader_time)
336                 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
337         if (t->kernel_time)
338                 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
339         if (t->initrd_time > 0)
340                 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
341
342         size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
343         if (t->kernel_time > 0)
344                 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
345         else
346                 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
347
348         ptr = strdup(buf);
349         if (!ptr)
350                 return log_oom();
351
352         *_buf = ptr;
353         return 0;
354 }
355
356 static void svg_graph_box(double height, double begin, double end) {
357         long long i;
358
359         /* outside box, fill */
360         svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
361             SCALE_X * (end - begin), SCALE_Y * height);
362
363         for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
364                 /* lines for each second */
365                 if (i % 5000000 == 0)
366                         svg("  <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
367                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
368                             SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
369                 else if (i % 1000000 == 0)
370                         svg("  <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
371                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
372                             SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
373                 else
374                         svg("  <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
375                             SCALE_X * i, SCALE_X * i, SCALE_Y * height);
376         }
377 }
378
379 static int analyze_plot(DBusConnection *bus) {
380         struct unit_times *times;
381         struct boot_times *boot;
382         struct utsname name;
383         int n, m = 1, y=0;
384         double width;
385         _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
386         struct unit_times *u;
387
388         n = acquire_boot_times(bus, &boot);
389         if (n < 0)
390                 return n;
391
392         n = pretty_boot_time(bus, &pretty_times);
393         if (n < 0)
394                 return n;
395
396         get_os_name(&osname);
397         assert_se(uname(&name) >= 0);
398
399         n = acquire_time_data(bus, &times);
400         if (n <= 0)
401                 return n;
402
403         qsort(times, n, sizeof(struct unit_times), compare_unit_start);
404
405         width = SCALE_X * (boot->firmware_time + boot->finish_time);
406         if (width < 800.0)
407                 width = 800.0;
408
409         if (boot->firmware_time > boot->loader_time)
410                 m++;
411         if (boot->loader_time) {
412                 m++;
413                 if (width < 1000.0)
414                         width = 1000.0;
415         }
416         if (boot->initrd_time)
417                 m++;
418         if (boot->kernel_time)
419                 m++;
420
421         for (u = times; u < times + n; u++) {
422                 double len;
423
424                 if (u->ixt < boot->userspace_time ||
425                     u->ixt > boot->finish_time) {
426                         free(u->name);
427                         u->name = NULL;
428                         continue;
429                 }
430                 len = ((boot->firmware_time + u->ixt) * SCALE_X)
431                         + (10.0 * strlen(u->name));
432                 if (len > width)
433                         width = len;
434
435                 if (u->iet > u->ixt && u->iet <= boot->finish_time
436                                 && u->aet == 0 && u->axt == 0)
437                         u->aet = u->axt = u->iet;
438                 if (u->aet < u->ixt || u->aet > boot->finish_time)
439                         u->aet = boot->finish_time;
440                 if (u->axt < u->aet || u->aet > boot->finish_time)
441                         u->axt = boot->finish_time;
442                 if (u->iet < u->axt || u->iet > boot->finish_time)
443                         u->iet = boot->finish_time;
444                 m++;
445         }
446
447         svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
448             "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
449             "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
450
451         svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
452             "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
453                         80.0 + width, 150.0 + (m * SCALE_Y));
454
455         /* write some basic info as a comment, including some help */
456         svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a   -->\n"
457             "<!-- browser such as Chrome, Chromium or Firefox. Other applications     -->\n"
458             "<!-- that render these files properly but much slower are ImageMagick,   -->\n"
459             "<!-- gimp, inkscape, etc. To display the files on your system, just      -->\n"
460             "<!-- point your browser to this file.                                    -->\n\n"
461             "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
462
463         /* style sheet */
464         svg("<defs>\n  <style type=\"text/css\">\n    <![CDATA[\n"
465             "      rect       { stroke-width: 1; stroke-opacity: 0; }\n"
466             "      rect.activating   { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
467             "      rect.active       { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
468             "      rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
469             "      rect.kernel       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
470             "      rect.initrd       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
471             "      rect.firmware     { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
472             "      rect.loader       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
473             "      rect.userspace    { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
474             "      rect.box   { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
475             "      line       { stroke: rgb(64,64,64); stroke-width: 1; }\n"
476             "//    line.sec1  { }\n"
477             "      line.sec5  { stroke-width: 2; }\n"
478             "      line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
479             "      text       { font-family: Verdana, Helvetica; font-size: 10; }\n"
480             "      text.left  { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
481             "      text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
482             "      text.sec   { font-size: 8; }\n"
483             "    ]]>\n   </style>\n</defs>\n\n");
484
485         svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
486         svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
487             isempty(osname) ? "Linux" : osname,
488             name.nodename, name.release, name.version, name.machine);
489         svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
490                         120.0 + (m *SCALE_Y));
491
492         svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
493         svg_graph_box(m, -boot->firmware_time, boot->finish_time);
494
495         if (boot->firmware_time) {
496                 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
497                 svg_text(true, -(double) boot->firmware_time, y, "firmware");
498                 y++;
499         }
500         if (boot->loader_time) {
501                 svg_bar("loader", -(double) boot->loader_time, 0, y);
502                 svg_text(true, -(double) boot->loader_time, y, "loader");
503                 y++;
504         }
505         if (boot->kernel_time) {
506                 svg_bar("kernel", 0, boot->kernel_done_time, y);
507                 svg_text(true, 0, y, "kernel");
508                 y++;
509         }
510         if (boot->initrd_time) {
511                 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
512                 svg_text(true, boot->initrd_time, y, "initrd");
513                 y++;
514         }
515         svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
516         svg_text("left", boot->userspace_time, y, "userspace");
517         y++;
518
519         for (u = times; u < times + n; u++) {
520                 char ts[FORMAT_TIMESPAN_MAX];
521                 bool b;
522
523                 if (!u->name)
524                         continue;
525
526                 svg_bar("activating",   u->ixt, u->aet, y);
527                 svg_bar("active",       u->aet, u->axt, y);
528                 svg_bar("deactivating", u->axt, u->iet, y);
529
530                 b = u->ixt * SCALE_X > width * 2 / 3;
531                 if (u->time)
532                         svg_text(b, u->ixt, y, "%s (%s)",
533                                  u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
534                 else
535                         svg_text(b, u->ixt, y, "%s", u->name);
536                 y++;
537         }
538         svg("</g>\n\n");
539
540         svg("</svg>");
541
542         free_unit_times(times, (unsigned) n);
543
544         return 0;
545 }
546
547 static int analyze_blame(DBusConnection *bus) {
548         struct unit_times *times;
549         unsigned i;
550         int n;
551
552         n = acquire_time_data(bus, &times);
553         if (n <= 0)
554                 return n;
555
556         qsort(times, n, sizeof(struct unit_times), compare_unit_time);
557
558         for (i = 0; i < (unsigned) n; i++) {
559                 char ts[FORMAT_TIMESPAN_MAX];
560
561                 if (times[i].time > 0)
562                         printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
563         }
564
565         free_unit_times(times, (unsigned) n);
566         return 0;
567 }
568
569 static int analyze_time(DBusConnection *bus) {
570         _cleanup_free_ char *buf = NULL;
571         int r;
572
573         r = pretty_boot_time(bus, &buf);
574         if (r < 0)
575                 return r;
576
577         puts(buf);
578         return 0;
579 }
580
581 static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter) {
582
583         static const char * const colors[] = {
584                 "Requires",              "[color=\"black\"]",
585                 "RequiresOverridable",   "[color=\"black\"]",
586                 "Requisite",             "[color=\"darkblue\"]",
587                 "RequisiteOverridable",  "[color=\"darkblue\"]",
588                 "Wants",                 "[color=\"grey66\"]",
589                 "Conflicts",             "[color=\"red\"]",
590                 "ConflictedBy",          "[color=\"red\"]",
591                 "After",                 "[color=\"green\"]"
592         };
593
594         const char *c = NULL;
595         unsigned i;
596
597         assert(name);
598         assert(prop);
599         assert(iter);
600
601         for (i = 0; i < ELEMENTSOF(colors); i += 2)
602                 if (streq(colors[i], prop)) {
603                         c = colors[i+1];
604                         break;
605                 }
606
607         if (!c)
608                 return 0;
609
610         if (arg_dot != DEP_ALL)
611                 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
612                         return 0;
613
614         if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
615             dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
616                 DBusMessageIter sub;
617
618                 dbus_message_iter_recurse(iter, &sub);
619
620                 for (dbus_message_iter_recurse(iter, &sub);
621                      dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
622                      dbus_message_iter_next(&sub)) {
623                         const char *s;
624
625                         assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
626                         dbus_message_iter_get_basic(&sub, &s);
627                         printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
628                 }
629         }
630
631         return 0;
632 }
633
634 static int graph_one(DBusConnection *bus, const struct unit_info *u) {
635         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
636         const char *interface = "org.freedesktop.systemd1.Unit";
637         int r;
638         DBusMessageIter iter, sub, sub2, sub3;
639
640         assert(bus);
641         assert(u);
642
643         r = bus_method_call_with_reply(
644                         bus,
645                         "org.freedesktop.systemd1",
646                         u->unit_path,
647                         "org.freedesktop.DBus.Properties",
648                         "GetAll",
649                         &reply,
650                         NULL,
651                         DBUS_TYPE_STRING, &interface,
652                         DBUS_TYPE_INVALID);
653         if (r < 0)
654                 return r;
655
656         if (!dbus_message_iter_init(reply, &iter) ||
657             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
658             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
659                 log_error("Failed to parse reply.");
660                 return -EIO;
661         }
662
663         for (dbus_message_iter_recurse(&iter, &sub);
664              dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
665              dbus_message_iter_next(&sub)) {
666                 const char *prop;
667
668                 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
669                 dbus_message_iter_recurse(&sub, &sub2);
670
671                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
672                     dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
673                         log_error("Failed to parse reply.");
674                         return -EIO;
675                 }
676
677                 dbus_message_iter_recurse(&sub2, &sub3);
678                 r = graph_one_property(u->id, prop, &sub3);
679                 if (r < 0)
680                         return r;
681         }
682
683         return 0;
684 }
685
686 static int dot(DBusConnection *bus) {
687         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
688         DBusMessageIter iter, sub;
689         int r;
690
691         r = bus_method_call_with_reply(
692                         bus,
693                         "org.freedesktop.systemd1",
694                         "/org/freedesktop/systemd1",
695                         "org.freedesktop.systemd1.Manager",
696                         "ListUnits",
697                         &reply,
698                         NULL,
699                         DBUS_TYPE_INVALID);
700         if (r < 0)
701                 return r;
702
703         if (!dbus_message_iter_init(reply, &iter) ||
704             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
705             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
706                 log_error("Failed to parse reply.");
707                 return -EIO;
708         }
709
710         printf("digraph systemd {\n");
711
712         for (dbus_message_iter_recurse(&iter, &sub);
713              dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
714              dbus_message_iter_next(&sub)) {
715                 struct unit_info u;
716
717                 r = bus_parse_unit_info(&sub, &u);
718                 if (r < 0)
719                         return -EIO;
720
721                 r = graph_one(bus, &u);
722                 if (r < 0)
723                         return r;
724         }
725
726         printf("}\n");
727
728         log_info("   Color legend: black     = Requires\n"
729                  "                 dark blue = Requisite\n"
730                  "                 dark grey = Wants\n"
731                  "                 red       = Conflicts\n"
732                  "                 green     = After\n");
733
734         if (on_tty())
735                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
736                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
737
738         return 0;
739 }
740
741 static void analyze_help(void)
742 {
743         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
744                "Process systemd profiling information\n\n"
745                "  -h --help           Show this help\n"
746                "     --version        Show package version\n"
747                "     --system         Connect to system manager\n"
748                "     --user           Connect to user service manager\n"
749                "     --order          When generating a dependency graph, show only order\n"
750                "     --require        When generating a dependency graph, show only requirement\n\n"
751                "Commands:\n"
752                "  time                Print time spent in the kernel before reaching userspace\n"
753                "  blame               Print list of running units ordered by time to init\n"
754                "  plot                Output SVG graphic showing service initialization\n"
755                "  dot                 Dump dependency graph (in dot(1) format)\n\n",
756                program_invocation_short_name);
757 }
758
759 static int parse_argv(int argc, char *argv[])
760 {
761         enum {
762                 ARG_VERSION = 0x100,
763                 ARG_ORDER,
764                 ARG_REQUIRE,
765                 ARG_USER,
766                 ARG_SYSTEM
767         };
768
769         static const struct option options[] = {
770                 { "help",      no_argument,       NULL, 'h'           },
771                 { "version",   no_argument,       NULL, ARG_VERSION   },
772                 { "order",     no_argument,       NULL, ARG_ORDER     },
773                 { "require",   no_argument,       NULL, ARG_REQUIRE   },
774                 { "user",      no_argument,       NULL, ARG_USER      },
775                 { "system",    no_argument,       NULL, ARG_SYSTEM    },
776                 { NULL,        0,                 NULL, 0             }
777         };
778
779         assert(argc >= 0);
780         assert(argv);
781
782         for (;;) {
783                 switch (getopt_long(argc, argv, "h", options, NULL)) {
784
785                 case 'h':
786                         analyze_help();
787                         return 0;
788
789                 case ARG_VERSION:
790                         puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
791                         return 0;
792
793                 case ARG_USER:
794                         arg_scope = UNIT_FILE_USER;
795                         break;
796
797                 case ARG_SYSTEM:
798                         arg_scope = UNIT_FILE_SYSTEM;
799                         break;
800
801                 case ARG_ORDER:
802                         arg_dot = DEP_ORDER;
803                         break;
804
805                 case ARG_REQUIRE:
806                         arg_dot = DEP_REQUIRE;
807                         break;
808
809                 case -1:
810                         return 1;
811
812                 case '?':
813                         return -EINVAL;
814
815                 default:
816                         assert_not_reached("Unhandled option");
817                 }
818         }
819 }
820
821 int main(int argc, char *argv[]) {
822         int r;
823         DBusConnection *bus = NULL;
824
825         setlocale(LC_ALL, "");
826         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
827         log_parse_environment();
828         log_open();
829
830         r = parse_argv(argc, argv);
831         if (r < 0)
832                 return EXIT_FAILURE;
833         else if (r <= 0)
834                 return EXIT_SUCCESS;
835
836         bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
837         if (!bus)
838                 return EXIT_FAILURE;
839
840         if (!argv[optind] || streq(argv[optind], "time"))
841                 r = analyze_time(bus);
842         else if (streq(argv[optind], "blame"))
843                 r = analyze_blame(bus);
844         else if (streq(argv[optind], "plot"))
845                 r = analyze_plot(bus);
846         else if (streq(argv[optind], "dot"))
847                 r = dot(bus);
848         else
849                 log_error("Unknown operation '%s'.", argv[optind]);
850
851         dbus_connection_unref(bus);
852
853         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
854 }