chiark / gitweb /
clients: add missing command line arguments
[elogind.git] / src / analyze / 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 "sd-bus.h"
31 #include "bus-util.h"
32 #include "bus-error.h"
33 #include "install.h"
34 #include "log.h"
35 #include "build.h"
36 #include "util.h"
37 #include "strxcpyx.h"
38 #include "fileio.h"
39 #include "strv.h"
40 #include "unit-name.h"
41 #include "special.h"
42 #include "hashmap.h"
43 #include "pager.h"
44
45 #define SCALE_X (0.1 / 1000.0)   /* pixels per us */
46 #define SCALE_Y 20.0
47
48 #define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
49
50 #define svg(...) printf(__VA_ARGS__)
51
52 #define svg_bar(class, x1, x2, y)                                       \
53         svg("  <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
54             (class),                                                    \
55             SCALE_X * (x1), SCALE_Y * (y),                              \
56             SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
57
58 #define svg_text(b, x, y, format, ...)                                  \
59         do {                                                            \
60                 svg("  <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
61                 svg(format, ## __VA_ARGS__);                            \
62                 svg("</text>\n");                                       \
63         } while(false)
64
65 static enum dot {
66         DEP_ALL,
67         DEP_ORDER,
68         DEP_REQUIRE
69 } arg_dot = DEP_ALL;
70 static char** arg_dot_from_patterns = NULL;
71 static char** arg_dot_to_patterns = NULL;
72 static usec_t arg_fuzz = 0;
73 static bool arg_no_pager = false;
74 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
75 static char *arg_host = NULL;
76 static bool arg_user = false;
77
78 struct boot_times {
79         usec_t firmware_time;
80         usec_t loader_time;
81         usec_t kernel_time;
82         usec_t kernel_done_time;
83         usec_t initrd_time;
84         usec_t userspace_time;
85         usec_t finish_time;
86         usec_t generators_start_time;
87         usec_t generators_finish_time;
88         usec_t unitsload_start_time;
89         usec_t unitsload_finish_time;
90 };
91
92 struct unit_times {
93         char *name;
94         usec_t activating;
95         usec_t activated;
96         usec_t deactivated;
97         usec_t deactivating;
98         usec_t time;
99 };
100
101 static void pager_open_if_enabled(void) {
102
103         if (arg_no_pager)
104                 return;
105
106         pager_open(false);
107 }
108
109 static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
110         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
111         int r;
112
113         assert(bus);
114         assert(path);
115         assert(interface);
116         assert(property);
117         assert(val);
118
119         r = sd_bus_get_property_trivial(
120                         bus,
121                         "org.freedesktop.systemd1",
122                         path,
123                         interface,
124                         property,
125                         &error,
126                         't', val);
127
128         if (r < 0) {
129                 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
130                 return r;
131         }
132
133         return 0;
134 }
135
136 static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) {
137         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
138         int r;
139
140         assert(bus);
141         assert(path);
142         assert(property);
143         assert(strv);
144
145         r = sd_bus_get_property_strv(
146                         bus,
147                         "org.freedesktop.systemd1",
148                         path,
149                         "org.freedesktop.systemd1.Unit",
150                         property,
151                         &error,
152                         strv);
153         if (r < 0) {
154                 log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
155                 return r;
156         }
157
158         return 0;
159 }
160
161 static int compare_unit_time(const void *a, const void *b) {
162         return compare(((struct unit_times *)b)->time,
163                        ((struct unit_times *)a)->time);
164 }
165
166 static int compare_unit_start(const void *a, const void *b) {
167         return compare(((struct unit_times *)a)->activating,
168                        ((struct unit_times *)b)->activating);
169 }
170
171 static int get_os_name(char **_n) {
172         char *n = NULL;
173         int r;
174
175         r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
176         if (r < 0)
177                 return r;
178
179         if (!n)
180                 return -ENOENT;
181
182         *_n = n;
183         return 0;
184 }
185
186 static void free_unit_times(struct unit_times *t, unsigned n) {
187         struct unit_times *p;
188
189         for (p = t; p < t + n; p++)
190                 free(p->name);
191
192         free(t);
193 }
194
195 static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
196         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
197         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
198         int r, c = 0;
199         struct unit_times *unit_times = NULL;
200         size_t size = 0;
201         UnitInfo u;
202
203         r = sd_bus_call_method(
204                         bus,
205                         "org.freedesktop.systemd1",
206                         "/org/freedesktop/systemd1",
207                         "org.freedesktop.systemd1.Manager",
208                         "ListUnits",
209                         &error, &reply,
210                         NULL);
211         if (r < 0) {
212                 log_error("Failed to list units: %s", bus_error_message(&error, -r));
213                 goto fail;
214         }
215
216         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
217         if (r < 0) {
218                 bus_log_parse_error(r);
219                 goto fail;
220         }
221
222         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
223                 struct unit_times *t;
224
225                 if (!GREEDY_REALLOC(unit_times, size, c+1)) {
226                         r = log_oom();
227                         goto fail;
228                 }
229
230                 t = unit_times+c;
231                 t->name = NULL;
232
233                 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
234
235                 if (bus_get_uint64_property(bus, u.unit_path,
236                                             "org.freedesktop.systemd1.Unit",
237                                             "InactiveExitTimestampMonotonic",
238                                             &t->activating) < 0 ||
239                     bus_get_uint64_property(bus, u.unit_path,
240                                             "org.freedesktop.systemd1.Unit",
241                                             "ActiveEnterTimestampMonotonic",
242                                             &t->activated) < 0 ||
243                     bus_get_uint64_property(bus, u.unit_path,
244                                             "org.freedesktop.systemd1.Unit",
245                                             "ActiveExitTimestampMonotonic",
246                                             &t->deactivating) < 0 ||
247                     bus_get_uint64_property(bus, u.unit_path,
248                                             "org.freedesktop.systemd1.Unit",
249                                             "InactiveEnterTimestampMonotonic",
250                                             &t->deactivated) < 0) {
251                         r = -EIO;
252                         goto fail;
253                 }
254
255                 if (t->activated >= t->activating)
256                         t->time = t->activated - t->activating;
257                 else if (t->deactivated >= t->activating)
258                         t->time = t->deactivated - t->activating;
259                 else
260                         t->time = 0;
261
262                 if (t->activating == 0)
263                         continue;
264
265                 t->name = strdup(u.id);
266                 if (t->name == NULL) {
267                         r = log_oom();
268                         goto fail;
269                 }
270                 c++;
271         }
272         if (r < 0) {
273                 bus_log_parse_error(r);
274                 goto fail;
275         }
276
277         *out = unit_times;
278         return c;
279
280 fail:
281         free_unit_times(unit_times, (unsigned) c);
282         return r;
283 }
284
285 static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) {
286         static struct boot_times times;
287         static bool cached = false;
288
289         if (cached)
290                 goto finish;
291
292         assert_cc(sizeof(usec_t) == sizeof(uint64_t));
293
294         if (bus_get_uint64_property(bus,
295                                     "/org/freedesktop/systemd1",
296                                     "org.freedesktop.systemd1.Manager",
297                                     "FirmwareTimestampMonotonic",
298                                     &times.firmware_time) < 0 ||
299             bus_get_uint64_property(bus,
300                                     "/org/freedesktop/systemd1",
301                                     "org.freedesktop.systemd1.Manager",
302                                     "LoaderTimestampMonotonic",
303                                     &times.loader_time) < 0 ||
304             bus_get_uint64_property(bus,
305                                     "/org/freedesktop/systemd1",
306                                     "org.freedesktop.systemd1.Manager",
307                                     "KernelTimestamp",
308                                     &times.kernel_time) < 0 ||
309             bus_get_uint64_property(bus,
310                                     "/org/freedesktop/systemd1",
311                                     "org.freedesktop.systemd1.Manager",
312                                     "InitRDTimestampMonotonic",
313                                     &times.initrd_time) < 0 ||
314             bus_get_uint64_property(bus,
315                                     "/org/freedesktop/systemd1",
316                                     "org.freedesktop.systemd1.Manager",
317                                     "UserspaceTimestampMonotonic",
318                                     &times.userspace_time) < 0 ||
319             bus_get_uint64_property(bus,
320                                     "/org/freedesktop/systemd1",
321                                     "org.freedesktop.systemd1.Manager",
322                                     "FinishTimestampMonotonic",
323                                     &times.finish_time) < 0 ||
324             bus_get_uint64_property(bus,
325                                     "/org/freedesktop/systemd1",
326                                     "org.freedesktop.systemd1.Manager",
327                                     "GeneratorsStartTimestampMonotonic",
328                                     &times.generators_start_time) < 0 ||
329             bus_get_uint64_property(bus,
330                                     "/org/freedesktop/systemd1",
331                                     "org.freedesktop.systemd1.Manager",
332                                     "GeneratorsFinishTimestampMonotonic",
333                                     &times.generators_finish_time) < 0 ||
334             bus_get_uint64_property(bus,
335                                     "/org/freedesktop/systemd1",
336                                     "org.freedesktop.systemd1.Manager",
337                                     "UnitsLoadStartTimestampMonotonic",
338                                     &times.unitsload_start_time) < 0 ||
339             bus_get_uint64_property(bus,
340                                     "/org/freedesktop/systemd1",
341                                     "org.freedesktop.systemd1.Manager",
342                                     "UnitsLoadFinishTimestampMonotonic",
343                                     &times.unitsload_finish_time) < 0)
344                 return -EIO;
345
346         if (times.finish_time <= 0) {
347                 log_error("Bootup is not yet finished. Please try again later.");
348                 return -EINPROGRESS;
349         }
350
351         if (times.initrd_time)
352                 times.kernel_done_time = times.initrd_time;
353         else
354                 times.kernel_done_time = times.userspace_time;
355
356         cached = true;
357
358 finish:
359         *bt = &times;
360         return 0;
361 }
362
363 static int pretty_boot_time(sd_bus *bus, char **_buf) {
364         char ts[FORMAT_TIMESPAN_MAX];
365         struct boot_times *t;
366         static char buf[4096];
367         size_t size;
368         char *ptr;
369         int r;
370
371         r = acquire_boot_times(bus, &t);
372         if (r < 0)
373                 return r;
374
375         ptr = buf;
376         size = sizeof(buf);
377
378         size = strpcpyf(&ptr, size, "Startup finished in ");
379         if (t->firmware_time)
380                 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
381         if (t->loader_time)
382                 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
383         if (t->kernel_time)
384                 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
385         if (t->initrd_time > 0)
386                 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
387
388         size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
389         if (t->kernel_time > 0)
390                 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
391         else
392                 strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
393
394         ptr = strdup(buf);
395         if (!ptr)
396                 return log_oom();
397
398         *_buf = ptr;
399         return 0;
400 }
401
402 static void svg_graph_box(double height, double begin, double end) {
403         long long i;
404
405         /* outside box, fill */
406         svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
407             SCALE_X * (end - begin), SCALE_Y * height);
408
409         for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
410                 /* lines for each second */
411                 if (i % 5000000 == 0)
412                         svg("  <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
413                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
414                             SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
415                 else if (i % 1000000 == 0)
416                         svg("  <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
417                             "  <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
418                             SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
419                 else
420                         svg("  <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
421                             SCALE_X * i, SCALE_X * i, SCALE_Y * height);
422         }
423 }
424
425 static int analyze_plot(sd_bus *bus) {
426         struct unit_times *times;
427         struct boot_times *boot;
428         struct utsname name;
429         int n, m = 1, y=0;
430         double width;
431         _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
432         struct unit_times *u;
433
434         n = acquire_boot_times(bus, &boot);
435         if (n < 0)
436                 return n;
437
438         n = pretty_boot_time(bus, &pretty_times);
439         if (n < 0)
440                 return n;
441
442         get_os_name(&osname);
443         assert_se(uname(&name) >= 0);
444
445         n = acquire_time_data(bus, &times);
446         if (n <= 0)
447                 return n;
448
449         qsort(times, n, sizeof(struct unit_times), compare_unit_start);
450
451         width = SCALE_X * (boot->firmware_time + boot->finish_time);
452         if (width < 800.0)
453                 width = 800.0;
454
455         if (boot->firmware_time > boot->loader_time)
456                 m++;
457         if (boot->loader_time) {
458                 m++;
459                 if (width < 1000.0)
460                         width = 1000.0;
461         }
462         if (boot->initrd_time)
463                 m++;
464         if (boot->kernel_time)
465                 m++;
466
467         for (u = times; u < times + n; u++) {
468                 double text_start, text_width;
469
470                 if (u->activating < boot->userspace_time ||
471                     u->activating > boot->finish_time) {
472                         free(u->name);
473                         u->name = NULL;
474                         continue;
475                 }
476
477                 /* If the text cannot fit on the left side then
478                  * increase the svg width so it fits on the right.
479                  * TODO: calculate the text width more accurately */
480                 text_width = 8.0 * strlen(u->name);
481                 text_start = (boot->firmware_time + u->activating) * SCALE_X;
482                 if (text_width > text_start && text_width + text_start > width)
483                         width = text_width + text_start;
484
485                 if (u->deactivated > u->activating && u->deactivated <= boot->finish_time
486                                 && u->activated == 0 && u->deactivating == 0)
487                         u->activated = u->deactivating = u->deactivated;
488                 if (u->activated < u->activating || u->activated > boot->finish_time)
489                         u->activated = boot->finish_time;
490                 if (u->deactivating < u->activated || u->activated > boot->finish_time)
491                         u->deactivating = boot->finish_time;
492                 if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
493                         u->deactivated = boot->finish_time;
494                 m++;
495         }
496
497         svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
498             "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
499             "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
500
501         svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
502             "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
503                         80.0 + width, 150.0 + (m * SCALE_Y) +
504                         5 * SCALE_Y /* legend */);
505
506         /* write some basic info as a comment, including some help */
507         svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a   -->\n"
508             "<!-- browser such as Chrome, Chromium or Firefox. Other applications     -->\n"
509             "<!-- that render these files properly but much slower are ImageMagick,   -->\n"
510             "<!-- gimp, inkscape, etc. To display the files on your system, just      -->\n"
511             "<!-- point your browser to this file.                                    -->\n\n"
512             "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
513
514         /* style sheet */
515         svg("<defs>\n  <style type=\"text/css\">\n    <![CDATA[\n"
516             "      rect       { stroke-width: 1; stroke-opacity: 0; }\n"
517             "      rect.background   { fill: rgb(255,255,255); }\n"
518             "      rect.activating   { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
519             "      rect.active       { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
520             "      rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
521             "      rect.kernel       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
522             "      rect.initrd       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
523             "      rect.firmware     { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
524             "      rect.loader       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
525             "      rect.userspace    { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
526             "      rect.generators   { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
527             "      rect.unitsload    { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
528             "      rect.box   { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
529             "      line       { stroke: rgb(64,64,64); stroke-width: 1; }\n"
530             "//    line.sec1  { }\n"
531             "      line.sec5  { stroke-width: 2; }\n"
532             "      line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
533             "      text       { font-family: Verdana, Helvetica; font-size: 14px; }\n"
534             "      text.left  { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
535             "      text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
536             "      text.sec   { font-size: 10px; }\n"
537             "    ]]>\n   </style>\n</defs>\n\n");
538
539         svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
540         svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
541         svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
542             isempty(osname) ? "Linux" : osname,
543             name.nodename, name.release, name.version, name.machine);
544
545         svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
546         svg_graph_box(m, -boot->firmware_time, boot->finish_time);
547
548         if (boot->firmware_time) {
549                 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
550                 svg_text(true, -(double) boot->firmware_time, y, "firmware");
551                 y++;
552         }
553         if (boot->loader_time) {
554                 svg_bar("loader", -(double) boot->loader_time, 0, y);
555                 svg_text(true, -(double) boot->loader_time, y, "loader");
556                 y++;
557         }
558         if (boot->kernel_time) {
559                 svg_bar("kernel", 0, boot->kernel_done_time, y);
560                 svg_text(true, 0, y, "kernel");
561                 y++;
562         }
563         if (boot->initrd_time) {
564                 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
565                 svg_text(true, boot->initrd_time, y, "initrd");
566                 y++;
567         }
568         svg_bar("active", boot->userspace_time, boot->finish_time, y);
569         svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
570         svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
571         svg_text(true, boot->userspace_time, y, "systemd");
572         y++;
573
574         for (u = times; u < times + n; u++) {
575                 char ts[FORMAT_TIMESPAN_MAX];
576                 bool b;
577
578                 if (!u->name)
579                         continue;
580
581                 svg_bar("activating",   u->activating, u->activated, y);
582                 svg_bar("active",       u->activated, u->deactivating, y);
583                 svg_bar("deactivating", u->deactivating, u->deactivated, y);
584
585                 /* place the text on the left if we have passed the half of the svg width */
586                 b = u->activating * SCALE_X < width / 2;
587                 if (u->time)
588                         svg_text(b, u->activating, y, "%s (%s)",
589                                  u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
590                 else
591                         svg_text(b, u->activating, y, "%s", u->name);
592                 y++;
593         }
594
595         /* Legend */
596         y++;
597         svg_bar("activating", 0, 300000, y);
598         svg_text(true, 400000, y, "Activating");
599         y++;
600         svg_bar("active", 0, 300000, y);
601         svg_text(true, 400000, y, "Active");
602         y++;
603         svg_bar("deactivating", 0, 300000, y);
604         svg_text(true, 400000, y, "Deactivating");
605         y++;
606         svg_bar("generators", 0, 300000, y);
607         svg_text(true, 400000, y, "Generators");
608         y++;
609         svg_bar("unitsload", 0, 300000, y);
610         svg_text(true, 400000, y, "Loading unit files");
611         y++;
612
613         svg("</g>\n\n");
614
615         svg("</svg>\n");
616
617         free_unit_times(times, (unsigned) n);
618
619         return 0;
620 }
621
622 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
623                                    bool last, struct unit_times *times, struct boot_times *boot) {
624         unsigned int i;
625         char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
626
627         for (i = level; i != 0; i--)
628                 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
629
630         printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
631
632         if (times) {
633                 if (times->time)
634                         printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
635                                format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
636                                format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
637                 else if (times->activated > boot->userspace_time)
638                         printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
639                 else
640                         printf("%s", name);
641         } else
642                 printf("%s", name);
643         printf("\n");
644
645         return 0;
646 }
647
648 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
649         _cleanup_free_ char *path = NULL;
650
651         assert(bus);
652         assert(name);
653         assert(deps);
654
655         path = unit_dbus_path_from_name(name);
656         if (path == NULL)
657                 return -ENOMEM;
658
659         return bus_get_unit_property_strv(bus, path, "After", deps);
660 }
661
662 static Hashmap *unit_times_hashmap;
663
664 static int list_dependencies_compare(const void *_a, const void *_b) {
665         const char **a = (const char**) _a, **b = (const char**) _b;
666         usec_t usa = 0, usb = 0;
667         struct unit_times *times;
668
669         times = hashmap_get(unit_times_hashmap, *a);
670         if (times)
671                 usa = times->activated;
672         times = hashmap_get(unit_times_hashmap, *b);
673         if (times)
674                 usb = times->activated;
675
676         return usb - usa;
677 }
678
679 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
680                                  unsigned int branches) {
681         _cleanup_strv_free_ char **deps = NULL;
682         char **c;
683         int r = 0;
684         usec_t service_longest = 0;
685         int to_print = 0;
686         struct unit_times *times;
687         struct boot_times *boot;
688
689         if (strv_extend(units, name))
690                 return log_oom();
691
692         r = list_dependencies_get_dependencies(bus, name, &deps);
693         if (r < 0)
694                 return r;
695
696         qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
697
698         r = acquire_boot_times(bus, &boot);
699         if (r < 0)
700                 return r;
701
702         STRV_FOREACH(c, deps) {
703                 times = hashmap_get(unit_times_hashmap, *c);
704                 if (times
705                     && times->activated
706                     && times->activated <= boot->finish_time
707                     && (times->activated >= service_longest
708                         || service_longest == 0)) {
709                         service_longest = times->activated;
710                         break;
711                 }
712         }
713
714         if (service_longest == 0 )
715                 return r;
716
717         STRV_FOREACH(c, deps) {
718                 times = hashmap_get(unit_times_hashmap, *c);
719                 if (times && times->activated
720                     && times->activated <= boot->finish_time
721                     && (service_longest - times->activated) <= arg_fuzz) {
722                         to_print++;
723                 }
724         }
725
726         if(!to_print)
727                 return r;
728
729         STRV_FOREACH(c, deps) {
730                 times = hashmap_get(unit_times_hashmap, *c);
731                 if (!times
732                     || !times->activated
733                     || times->activated > boot->finish_time
734                     || service_longest - times->activated > arg_fuzz)
735                         continue;
736
737                 to_print--;
738
739                 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
740                 if (r < 0)
741                         return r;
742
743                 if (strv_contains(*units, *c)) {
744                         r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
745                                                     true, NULL, boot);
746                         if (r < 0)
747                                 return r;
748                         continue;
749                 }
750
751                 r = list_dependencies_one(bus, *c, level + 1, units,
752                                           (branches << 1) | (to_print ? 1 : 0));
753                 if (r < 0)
754                         return r;
755
756                 if (!to_print)
757                         break;
758         }
759         return 0;
760 }
761
762 static int list_dependencies(sd_bus *bus, const char *name) {
763         _cleanup_strv_free_ char **units = NULL;
764         char ts[FORMAT_TIMESPAN_MAX];
765         struct unit_times *times;
766         int r;
767         const char *path, *id;
768         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
769         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
770         struct boot_times *boot;
771
772         assert(bus);
773
774         path = unit_dbus_path_from_name(name);
775         if (path == NULL)
776                 return -ENOMEM;
777
778         r = sd_bus_get_property(
779                         bus,
780                         "org.freedesktop.systemd1",
781                         path,
782                         "org.freedesktop.systemd1.Unit",
783                         "Id",
784                         &error,
785                         &reply,
786                         "s");
787         if (r < 0) {
788                 log_error("Failed to get ID: %s", bus_error_message(&error, -r));
789                 return r;
790         }
791
792         r = sd_bus_message_read(reply, "s", &id);
793         if (r < 0)
794                 return bus_log_parse_error(r);
795
796         times = hashmap_get(unit_times_hashmap, id);
797
798         r = acquire_boot_times(bus, &boot);
799         if (r < 0)
800                 return r;
801
802         if (times) {
803                 if (times->time)
804                         printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
805                                format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
806                 else if (times->activated > boot->userspace_time)
807                         printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
808                 else
809                         printf("%s\n", id);
810         }
811
812         return list_dependencies_one(bus, name, 0, &units, 0);
813 }
814
815 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
816         struct unit_times *times;
817         unsigned int i;
818         Hashmap *h;
819         int n, r;
820
821         n = acquire_time_data(bus, &times);
822         if (n <= 0)
823                 return n;
824
825         h = hashmap_new(string_hash_func, string_compare_func);
826         if (!h)
827                 return -ENOMEM;
828
829         for (i = 0; i < (unsigned)n; i++) {
830                 r = hashmap_put(h, times[i].name, &times[i]);
831                 if (r < 0)
832                         return r;
833         }
834         unit_times_hashmap = h;
835
836         pager_open_if_enabled();
837
838         puts("The time after the unit is active or started is printed after the \"@\" character.\n"
839              "The time the unit takes to start is printed after the \"+\" character.\n");
840
841         if (!strv_isempty(names)) {
842                 char **name;
843                 STRV_FOREACH(name, names)
844                         list_dependencies(bus, *name);
845         } else
846                 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
847
848         hashmap_free(h);
849         free_unit_times(times, (unsigned) n);
850         return 0;
851 }
852
853 static int analyze_blame(sd_bus *bus) {
854         struct unit_times *times;
855         unsigned i;
856         int n;
857
858         n = acquire_time_data(bus, &times);
859         if (n <= 0)
860                 return n;
861
862         qsort(times, n, sizeof(struct unit_times), compare_unit_time);
863
864         pager_open_if_enabled();
865
866         for (i = 0; i < (unsigned) n; i++) {
867                 char ts[FORMAT_TIMESPAN_MAX];
868
869                 if (times[i].time > 0)
870                         printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
871         }
872
873         free_unit_times(times, (unsigned) n);
874         return 0;
875 }
876
877 static int analyze_time(sd_bus *bus) {
878         _cleanup_free_ char *buf = NULL;
879         int r;
880
881         r = pretty_boot_time(bus, &buf);
882         if (r < 0)
883                 return r;
884
885         puts(buf);
886         return 0;
887 }
888
889 static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[]) {
890         _cleanup_strv_free_ char **units = NULL;
891         char **unit;
892         int r;
893
894         assert(u);
895         assert(prop);
896         assert(color);
897
898         r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
899         if (r < 0)
900                 return r;
901
902         STRV_FOREACH(unit, units) {
903                 char **p;
904                 bool match_found;
905
906                 if (!strv_isempty(arg_dot_from_patterns)) {
907                         match_found = false;
908
909                         STRV_FOREACH(p, arg_dot_from_patterns)
910                                 if (fnmatch(*p, u->id, 0) == 0) {
911                                         match_found = true;
912                                         break;
913                                 }
914
915                         if (!match_found)
916                                 continue;
917                 }
918
919                 if (!strv_isempty(arg_dot_to_patterns)) {
920                         match_found = false;
921
922                         STRV_FOREACH(p, arg_dot_to_patterns)
923                                 if (fnmatch(*p, *unit, 0) == 0) {
924                                         match_found = true;
925                                         break;
926                                 }
927
928                         if (!match_found)
929                                 continue;
930                 }
931
932                 if (!strv_isempty(patterns)) {
933                         match_found = false;
934
935                         STRV_FOREACH(p, patterns)
936                                 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
937                                         match_found = true;
938                                         break;
939                                 }
940                         if (!match_found)
941                                 continue;
942                 }
943
944                 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
945         }
946
947         return 0;
948 }
949
950 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[]) {
951         int r;
952
953         assert(bus);
954         assert(u);
955
956         if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
957                 r = graph_one_property(bus, u, "After", "green", patterns);
958                 if (r < 0)
959                         return r;
960         }
961
962         if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
963                 r = graph_one_property(bus, u, "Requires", "black", patterns);
964                 if (r < 0)
965                         return r;
966                 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
967                 if (r < 0)
968                         return r;
969                 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
970                 if (r < 0)
971                         return r;
972                 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
973                 if (r < 0)
974                         return r;
975                 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
976                 if (r < 0)
977                         return r;
978                 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
979                 if (r < 0)
980                         return r;
981         }
982
983         return 0;
984 }
985
986 static int dot(sd_bus *bus, char* patterns[]) {
987         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
988         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
989         int r;
990         UnitInfo u;
991
992         r = sd_bus_call_method(
993                         bus,
994                        "org.freedesktop.systemd1",
995                        "/org/freedesktop/systemd1",
996                        "org.freedesktop.systemd1.Manager",
997                        "ListUnits",
998                        &error,
999                        &reply,
1000                        "");
1001         if (r < 0) {
1002                 log_error("Failed to list units: %s", bus_error_message(&error, -r));
1003                 return r;
1004         }
1005
1006         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1007         if (r < 0)
1008                 return bus_log_parse_error(r);
1009
1010         printf("digraph systemd {\n");
1011
1012         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1013
1014                 r = graph_one(bus, &u, patterns);
1015                 if (r < 0)
1016                         return r;
1017         }
1018         if (r < 0)
1019                 return bus_log_parse_error(r);
1020
1021         printf("}\n");
1022
1023         log_info("   Color legend: black     = Requires\n"
1024                  "                 dark blue = Requisite\n"
1025                  "                 dark grey = Wants\n"
1026                  "                 red       = Conflicts\n"
1027                  "                 green     = After\n");
1028
1029         if (on_tty())
1030                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1031                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1032
1033         return 0;
1034 }
1035
1036 static int dump(sd_bus *bus, char **args) {
1037         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1038         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1039         const char *text = NULL;
1040         int r;
1041
1042         if (!strv_isempty(args)) {
1043                 log_error("Too many arguments.");
1044                 return -E2BIG;
1045         }
1046
1047         pager_open_if_enabled();
1048
1049         r = sd_bus_call_method(
1050                         bus,
1051                        "org.freedesktop.systemd1",
1052                        "/org/freedesktop/systemd1",
1053                        "org.freedesktop.systemd1.Manager",
1054                        "Dump",
1055                        &error,
1056                        &reply,
1057                        "");
1058         if (r < 0) {
1059                 log_error("Failed issue method call: %s", bus_error_message(&error, -r));
1060                 return r;
1061         }
1062
1063         r = sd_bus_message_read(reply, "s", &text);
1064         if (r < 0)
1065                 return bus_log_parse_error(r);
1066
1067         fputs(text, stdout);
1068         return 0;
1069 }
1070
1071 static int set_log_level(sd_bus *bus, char **args) {
1072         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1073         int r;
1074
1075         assert(bus);
1076         assert(args);
1077
1078         if (strv_length(args) != 1) {
1079                 log_error("This command expects one argument only.");
1080                 return -E2BIG;
1081         }
1082
1083         r = sd_bus_set_property(
1084                         bus,
1085                         "org.freedesktop.systemd1",
1086                         "/org/freedesktop/systemd1",
1087                         "org.freedesktop.systemd1.Manager",
1088                         "LogLevel",
1089                         &error,
1090                         "s",
1091                         args[0]);
1092         if (r < 0) {
1093                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1094                 return -EIO;
1095         }
1096
1097         return 0;
1098 }
1099
1100 static int help(void) {
1101
1102         pager_open_if_enabled();
1103
1104         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1105                "Process systemd profiling information.\n\n"
1106                "  -h --help               Show this help\n"
1107                "     --version            Show package version\n"
1108                "     --no-pager           Do not pipe output into a pager\n"
1109                "     --system             Connect to system manager\n"
1110                "     --user               Connect to user manager\n"
1111                "  -H --host=[USER@]HOST   Operate on remote host\n"
1112                "  -M --machine=CONTAINER  Operate on local container\n"
1113                "     --order              When generating a dependency graph, show only order\n"
1114                "     --require            When generating a dependency graph, show only requirement\n"
1115                "     --from-pattern=GLOB, --to-pattern=GLOB\n"
1116                "                          When generating a dependency graph, filter only origins\n"
1117                "                          or destinations, respectively\n"
1118                "     --fuzz=TIMESPAN      When printing the tree of the critical chain, print also\n"
1119                "                          services, which finished TIMESPAN earlier, than the\n"
1120                "                          latest in the branch. The unit of TIMESPAN is seconds\n"
1121                "                          unless specified with a different unit, i.e. 50ms\n\n"
1122                "Commands:\n"
1123                "  time                    Print time spent in the kernel before reaching userspace\n"
1124                "  blame                   Print list of running units ordered by time to init\n"
1125                "  critical-chain          Print a tree of the time critical chain of units\n"
1126                "  plot                    Output SVG graphic showing service initialization\n"
1127                "  dot                     Output dependency graph in dot(1) format\n"
1128                "  set-log-level LEVEL     Set logging threshold for systemd\n"
1129                "  dump                    Output state serialization of service manager\n",
1130                program_invocation_short_name);
1131
1132         /* When updating this list, including descriptions, apply
1133          * changes to shell-completion/bash/systemd and
1134          * shell-completion/systemd-zsh-completion.zsh too. */
1135
1136         return 0;
1137 }
1138
1139 static int parse_argv(int argc, char *argv[]) {
1140         enum {
1141                 ARG_VERSION = 0x100,
1142                 ARG_ORDER,
1143                 ARG_REQUIRE,
1144                 ARG_USER,
1145                 ARG_SYSTEM,
1146                 ARG_DOT_FROM_PATTERN,
1147                 ARG_DOT_TO_PATTERN,
1148                 ARG_FUZZ,
1149                 ARG_NO_PAGER
1150         };
1151
1152         static const struct option options[] = {
1153                 { "help",         no_argument,       NULL, 'h'                  },
1154                 { "version",      no_argument,       NULL, ARG_VERSION          },
1155                 { "order",        no_argument,       NULL, ARG_ORDER            },
1156                 { "require",      no_argument,       NULL, ARG_REQUIRE          },
1157                 { "user",         no_argument,       NULL, ARG_USER             },
1158                 { "system",       no_argument,       NULL, ARG_SYSTEM           },
1159                 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1160                 { "to-pattern",   required_argument, NULL, ARG_DOT_TO_PATTERN   },
1161                 { "fuzz",         required_argument, NULL, ARG_FUZZ             },
1162                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER         },
1163                 { "host",         required_argument, NULL, 'H'                  },
1164                 { "machine",      required_argument, NULL, 'M'                  },
1165                 {}
1166         };
1167
1168         int r, c;
1169
1170         assert(argc >= 0);
1171         assert(argv);
1172
1173         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1174
1175                 switch (c) {
1176
1177                 case 'h':
1178                         return help();
1179
1180                 case ARG_VERSION:
1181                         puts(PACKAGE_STRING);
1182                         puts(SYSTEMD_FEATURES);
1183                         return 0;
1184
1185                 case ARG_USER:
1186                         arg_user = true;
1187                         break;
1188
1189                 case ARG_SYSTEM:
1190                         arg_user = false;
1191                         break;
1192
1193                 case ARG_ORDER:
1194                         arg_dot = DEP_ORDER;
1195                         break;
1196
1197                 case ARG_REQUIRE:
1198                         arg_dot = DEP_REQUIRE;
1199                         break;
1200
1201                 case ARG_DOT_FROM_PATTERN:
1202                         if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1203                                 return log_oom();
1204
1205                         break;
1206
1207                 case ARG_DOT_TO_PATTERN:
1208                         if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1209                                 return log_oom();
1210
1211                         break;
1212
1213                 case ARG_FUZZ:
1214                         r = parse_sec(optarg, &arg_fuzz);
1215                         if (r < 0)
1216                                 return r;
1217                         break;
1218
1219                 case ARG_NO_PAGER:
1220                         arg_no_pager = true;
1221                         break;
1222
1223                 case 'H':
1224                         arg_transport = BUS_TRANSPORT_REMOTE;
1225                         arg_host = optarg;
1226                         break;
1227
1228                 case 'M':
1229                         arg_transport = BUS_TRANSPORT_CONTAINER;
1230                         arg_host = optarg;
1231                         break;
1232
1233                 case '?':
1234                         return -EINVAL;
1235
1236                 default:
1237                         assert_not_reached("Unhandled option");
1238                 }
1239         }
1240
1241         return 1;
1242 }
1243
1244 int main(int argc, char *argv[]) {
1245         _cleanup_bus_unref_ sd_bus *bus = NULL;
1246         int r;
1247
1248         setlocale(LC_ALL, "");
1249         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1250         log_parse_environment();
1251         log_open();
1252
1253         r = parse_argv(argc, argv);
1254         if (r <= 0)
1255                 goto finish;
1256
1257         r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1258         if (r < 0) {
1259                 log_error("Failed to create bus connection: %s", strerror(-r));
1260                 goto finish;
1261         }
1262
1263         if (!argv[optind] || streq(argv[optind], "time"))
1264                 r = analyze_time(bus);
1265         else if (streq(argv[optind], "blame"))
1266                 r = analyze_blame(bus);
1267         else if (streq(argv[optind], "critical-chain"))
1268                 r = analyze_critical_chain(bus, argv+optind+1);
1269         else if (streq(argv[optind], "plot"))
1270                 r = analyze_plot(bus);
1271         else if (streq(argv[optind], "dot"))
1272                 r = dot(bus, argv+optind+1);
1273         else if (streq(argv[optind], "dump"))
1274                 r = dump(bus, argv+optind+1);
1275         else if (streq(argv[optind], "set-log-level"))
1276                 r = set_log_level(bus, argv+optind+1);
1277         else
1278                 log_error("Unknown operation '%s'.", argv[optind]);
1279
1280 finish:
1281         pager_close();
1282
1283         strv_free(arg_dot_from_patterns);
1284         strv_free(arg_dot_to_patterns);
1285
1286         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1287 }