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