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