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