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