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