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