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