chiark / gitweb /
systemd-analyze: filter dot output
[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 = true;
630
631                         assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
632                         dbus_message_iter_get_basic(&sub, &s);
633
634                         STRV_FOREACH(p, arg_dot_from_patterns) {
635                                 match_found = false;
636                                 if (fnmatch(*p, name, 0) == 0) {
637                                         match_found = true;
638                                         break;
639                                 }
640                         }
641                         if (!match_found)
642                                 continue;
643
644                         STRV_FOREACH(p, arg_dot_to_patterns) {
645                                 match_found = false;
646                                 if (fnmatch(*p, s, 0) == 0) {
647                                         match_found = true;
648                                         break;
649                                 }
650                         }
651                         if (!match_found)
652                                 continue;
653
654                         STRV_FOREACH(p, patterns) {
655                                 match_found = false;
656                                 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
657                                         match_found = true;
658                                         break;
659                                 }
660                         }
661                         if (!match_found)
662                                 continue;
663
664                         printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
665                 }
666         }
667
668         return 0;
669 }
670
671 static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
672         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
673         const char *interface = "org.freedesktop.systemd1.Unit";
674         int r;
675         DBusMessageIter iter, sub, sub2, sub3;
676
677         assert(bus);
678         assert(u);
679
680         r = bus_method_call_with_reply(
681                         bus,
682                         "org.freedesktop.systemd1",
683                         u->unit_path,
684                         "org.freedesktop.DBus.Properties",
685                         "GetAll",
686                         &reply,
687                         NULL,
688                         DBUS_TYPE_STRING, &interface,
689                         DBUS_TYPE_INVALID);
690         if (r < 0)
691                 return r;
692
693         if (!dbus_message_iter_init(reply, &iter) ||
694             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
695             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY)  {
696                 log_error("Failed to parse reply.");
697                 return -EIO;
698         }
699
700         for (dbus_message_iter_recurse(&iter, &sub);
701              dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
702              dbus_message_iter_next(&sub)) {
703                 const char *prop;
704
705                 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
706                 dbus_message_iter_recurse(&sub, &sub2);
707
708                 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
709                     dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
710                         log_error("Failed to parse reply.");
711                         return -EIO;
712                 }
713
714                 dbus_message_iter_recurse(&sub2, &sub3);
715                 r = graph_one_property(u->id, prop, &sub3, patterns);
716                 if (r < 0)
717                         return r;
718         }
719
720         return 0;
721 }
722
723 static int dot(DBusConnection *bus, char* patterns[]) {
724         _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
725         DBusMessageIter iter, sub;
726         int r;
727
728         r = bus_method_call_with_reply(
729                         bus,
730                         "org.freedesktop.systemd1",
731                         "/org/freedesktop/systemd1",
732                         "org.freedesktop.systemd1.Manager",
733                         "ListUnits",
734                         &reply,
735                         NULL,
736                         DBUS_TYPE_INVALID);
737         if (r < 0)
738                 return r;
739
740         if (!dbus_message_iter_init(reply, &iter) ||
741             dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
742             dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT)  {
743                 log_error("Failed to parse reply.");
744                 return -EIO;
745         }
746
747         printf("digraph systemd {\n");
748
749         for (dbus_message_iter_recurse(&iter, &sub);
750              dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
751              dbus_message_iter_next(&sub)) {
752                 struct unit_info u;
753
754                 r = bus_parse_unit_info(&sub, &u);
755                 if (r < 0)
756                         return -EIO;
757
758                 r = graph_one(bus, &u, patterns);
759                 if (r < 0)
760                         return r;
761         }
762
763         printf("}\n");
764
765         log_info("   Color legend: black     = Requires\n"
766                  "                 dark blue = Requisite\n"
767                  "                 dark grey = Wants\n"
768                  "                 red       = Conflicts\n"
769                  "                 green     = After\n");
770
771         if (on_tty())
772                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
773                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
774
775         return 0;
776 }
777
778 static void analyze_help(void)
779 {
780         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
781                "Process systemd profiling information\n\n"
782                "  -h --help           Show this help\n"
783                "     --version        Show package version\n"
784                "     --system         Connect to system manager\n"
785                "     --user           Connect to user service manager\n"
786                "     --order          When generating a dependency graph, show only order\n"
787                "     --require        When generating a dependency graph, show only requirement\n\n"
788                "Commands:\n"
789                "  time                Print time spent in the kernel before reaching userspace\n"
790                "  blame               Print list of running units ordered by time to init\n"
791                "  plot                Output SVG graphic showing service initialization\n"
792                "  dot                 Dump dependency graph (in dot(1) format)\n\n",
793                program_invocation_short_name);
794 }
795
796 static int parse_argv(int argc, char *argv[])
797 {
798         enum {
799                 ARG_VERSION = 0x100,
800                 ARG_ORDER,
801                 ARG_REQUIRE,
802                 ARG_USER,
803                 ARG_SYSTEM,
804                 ARG_DOT_FROM_PATTERN,
805                 ARG_DOT_TO_PATTERN
806         };
807
808         static const struct option options[] = {
809                 { "help",      no_argument,       NULL, 'h'           },
810                 { "version",   no_argument,       NULL, ARG_VERSION   },
811                 { "order",     no_argument,       NULL, ARG_ORDER     },
812                 { "require",   no_argument,       NULL, ARG_REQUIRE   },
813                 { "user",      no_argument,       NULL, ARG_USER      },
814                 { "system",    no_argument,       NULL, ARG_SYSTEM    },
815                 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
816                 { "to-pattern",   required_argument, NULL, ARG_DOT_TO_PATTERN  },
817                 { NULL,        0,                 NULL, 0             }
818         };
819
820         assert(argc >= 0);
821         assert(argv);
822
823         for (;;) {
824                 switch (getopt_long(argc, argv, "h", options, NULL)) {
825
826                 case 'h':
827                         analyze_help();
828                         return 0;
829
830                 case ARG_VERSION:
831                         puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
832                         return 0;
833
834                 case ARG_USER:
835                         arg_scope = UNIT_FILE_USER;
836                         break;
837
838                 case ARG_SYSTEM:
839                         arg_scope = UNIT_FILE_SYSTEM;
840                         break;
841
842                 case ARG_ORDER:
843                         arg_dot = DEP_ORDER;
844                         break;
845
846                 case ARG_REQUIRE:
847                         arg_dot = DEP_REQUIRE;
848                         break;
849
850                 case ARG_DOT_FROM_PATTERN:
851                         arg_dot_from_patterns = strv_append(arg_dot_from_patterns, optarg);
852                         break;
853
854                 case ARG_DOT_TO_PATTERN:
855                         arg_dot_to_patterns = strv_append(arg_dot_to_patterns, optarg);
856                         break;
857
858                 case -1:
859                         return 1;
860
861                 case '?':
862                         return -EINVAL;
863
864                 default:
865                         assert_not_reached("Unhandled option");
866                 }
867         }
868 }
869
870 int main(int argc, char *argv[]) {
871         int r;
872         DBusConnection *bus = NULL;
873
874         setlocale(LC_ALL, "");
875         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
876         log_parse_environment();
877         log_open();
878
879         r = parse_argv(argc, argv);
880         if (r < 0)
881                 return EXIT_FAILURE;
882         else if (r <= 0)
883                 return EXIT_SUCCESS;
884
885         bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
886         if (!bus)
887                 return EXIT_FAILURE;
888
889         if (!argv[optind] || streq(argv[optind], "time"))
890                 r = analyze_time(bus);
891         else if (streq(argv[optind], "blame"))
892                 r = analyze_blame(bus);
893         else if (streq(argv[optind], "plot"))
894                 r = analyze_plot(bus);
895         else if (streq(argv[optind], "dot"))
896                 r = dot(bus, argv+optind+1);
897         else
898                 log_error("Unknown operation '%s'.", argv[optind]);
899
900         strv_free(arg_dot_from_patterns);
901         strv_free(arg_dot_to_patterns);
902         dbus_connection_unref(bus);
903
904         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
905 }