chiark / gitweb /
Do not print invalid UTF-8 in error messages
[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, -(double) 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         svg("</g>\n");
610
611         /* Legend */
612         svg("<g transform=\"translate(20,100)\">\n");
613         y++;
614         svg_bar("activating", 0, 300000, y);
615         svg_text(true, 400000, y, "Activating");
616         y++;
617         svg_bar("active", 0, 300000, y);
618         svg_text(true, 400000, y, "Active");
619         y++;
620         svg_bar("deactivating", 0, 300000, y);
621         svg_text(true, 400000, y, "Deactivating");
622         y++;
623         svg_bar("security", 0, 300000, y);
624         svg_text(true, 400000, y, "Setting up security module");
625         y++;
626         svg_bar("generators", 0, 300000, y);
627         svg_text(true, 400000, y, "Generators");
628         y++;
629         svg_bar("unitsload", 0, 300000, y);
630         svg_text(true, 400000, y, "Loading unit files");
631         y++;
632
633         svg("</g>\n\n");
634
635         svg("</svg>\n");
636
637         free_unit_times(times, (unsigned) n);
638
639         return 0;
640 }
641
642 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
643                                    bool last, struct unit_times *times, struct boot_times *boot) {
644         unsigned int i;
645         char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
646
647         for (i = level; i != 0; i--)
648                 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
649
650         printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
651
652         if (times) {
653                 if (times->time)
654                         printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
655                                format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC),
656                                format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
657                 else if (times->activated > boot->userspace_time)
658                         printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
659                 else
660                         printf("%s", name);
661         } else
662                 printf("%s", name);
663         printf("\n");
664
665         return 0;
666 }
667
668 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
669         _cleanup_free_ char *path = NULL;
670
671         assert(bus);
672         assert(name);
673         assert(deps);
674
675         path = unit_dbus_path_from_name(name);
676         if (path == NULL)
677                 return -ENOMEM;
678
679         return bus_get_unit_property_strv(bus, path, "After", deps);
680 }
681
682 static Hashmap *unit_times_hashmap;
683
684 static int list_dependencies_compare(const void *_a, const void *_b) {
685         const char **a = (const char**) _a, **b = (const char**) _b;
686         usec_t usa = 0, usb = 0;
687         struct unit_times *times;
688
689         times = hashmap_get(unit_times_hashmap, *a);
690         if (times)
691                 usa = times->activated;
692         times = hashmap_get(unit_times_hashmap, *b);
693         if (times)
694                 usb = times->activated;
695
696         return usb - usa;
697 }
698
699 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
700                                  unsigned int branches) {
701         _cleanup_strv_free_ char **deps = NULL;
702         char **c;
703         int r = 0;
704         usec_t service_longest = 0;
705         int to_print = 0;
706         struct unit_times *times;
707         struct boot_times *boot;
708
709         if (strv_extend(units, name))
710                 return log_oom();
711
712         r = list_dependencies_get_dependencies(bus, name, &deps);
713         if (r < 0)
714                 return r;
715
716         qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
717
718         r = acquire_boot_times(bus, &boot);
719         if (r < 0)
720                 return r;
721
722         STRV_FOREACH(c, deps) {
723                 times = hashmap_get(unit_times_hashmap, *c);
724                 if (times
725                     && times->activated
726                     && times->activated <= boot->finish_time
727                     && (times->activated >= service_longest
728                         || service_longest == 0)) {
729                         service_longest = times->activated;
730                         break;
731                 }
732         }
733
734         if (service_longest == 0 )
735                 return r;
736
737         STRV_FOREACH(c, deps) {
738                 times = hashmap_get(unit_times_hashmap, *c);
739                 if (times && times->activated
740                     && times->activated <= boot->finish_time
741                     && (service_longest - times->activated) <= arg_fuzz) {
742                         to_print++;
743                 }
744         }
745
746         if (!to_print)
747                 return r;
748
749         STRV_FOREACH(c, deps) {
750                 times = hashmap_get(unit_times_hashmap, *c);
751                 if (!times
752                     || !times->activated
753                     || times->activated > boot->finish_time
754                     || service_longest - times->activated > arg_fuzz)
755                         continue;
756
757                 to_print--;
758
759                 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
760                 if (r < 0)
761                         return r;
762
763                 if (strv_contains(*units, *c)) {
764                         r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
765                                                     true, NULL, boot);
766                         if (r < 0)
767                                 return r;
768                         continue;
769                 }
770
771                 r = list_dependencies_one(bus, *c, level + 1, units,
772                                           (branches << 1) | (to_print ? 1 : 0));
773                 if (r < 0)
774                         return r;
775
776                 if (!to_print)
777                         break;
778         }
779         return 0;
780 }
781
782 static int list_dependencies(sd_bus *bus, const char *name) {
783         _cleanup_strv_free_ char **units = NULL;
784         char ts[FORMAT_TIMESPAN_MAX];
785         struct unit_times *times;
786         int r;
787         const char *path, *id;
788         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
789         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
790         struct boot_times *boot;
791
792         assert(bus);
793
794         path = unit_dbus_path_from_name(name);
795         if (path == NULL)
796                 return -ENOMEM;
797
798         r = sd_bus_get_property(
799                         bus,
800                         "org.freedesktop.systemd1",
801                         path,
802                         "org.freedesktop.systemd1.Unit",
803                         "Id",
804                         &error,
805                         &reply,
806                         "s");
807         if (r < 0) {
808                 log_error("Failed to get ID: %s", bus_error_message(&error, -r));
809                 return r;
810         }
811
812         r = sd_bus_message_read(reply, "s", &id);
813         if (r < 0)
814                 return bus_log_parse_error(r);
815
816         times = hashmap_get(unit_times_hashmap, id);
817
818         r = acquire_boot_times(bus, &boot);
819         if (r < 0)
820                 return r;
821
822         if (times) {
823                 if (times->time)
824                         printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
825                                format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
826                 else if (times->activated > boot->userspace_time)
827                         printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC));
828                 else
829                         printf("%s\n", id);
830         }
831
832         return list_dependencies_one(bus, name, 0, &units, 0);
833 }
834
835 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
836         struct unit_times *times;
837         unsigned int i;
838         Hashmap *h;
839         int n, r;
840
841         n = acquire_time_data(bus, &times);
842         if (n <= 0)
843                 return n;
844
845         h = hashmap_new(string_hash_func, string_compare_func);
846         if (!h)
847                 return -ENOMEM;
848
849         for (i = 0; i < (unsigned)n; i++) {
850                 r = hashmap_put(h, times[i].name, &times[i]);
851                 if (r < 0)
852                         return r;
853         }
854         unit_times_hashmap = h;
855
856         pager_open_if_enabled();
857
858         puts("The time after the unit is active or started is printed after the \"@\" character.\n"
859              "The time the unit takes to start is printed after the \"+\" character.\n");
860
861         if (!strv_isempty(names)) {
862                 char **name;
863                 STRV_FOREACH(name, names)
864                         list_dependencies(bus, *name);
865         } else
866                 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
867
868         hashmap_free(h);
869         free_unit_times(times, (unsigned) n);
870         return 0;
871 }
872
873 static int analyze_blame(sd_bus *bus) {
874         struct unit_times *times;
875         unsigned i;
876         int n;
877
878         n = acquire_time_data(bus, &times);
879         if (n <= 0)
880                 return n;
881
882         qsort(times, n, sizeof(struct unit_times), compare_unit_time);
883
884         pager_open_if_enabled();
885
886         for (i = 0; i < (unsigned) n; i++) {
887                 char ts[FORMAT_TIMESPAN_MAX];
888
889                 if (times[i].time > 0)
890                         printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
891         }
892
893         free_unit_times(times, (unsigned) n);
894         return 0;
895 }
896
897 static int analyze_time(sd_bus *bus) {
898         _cleanup_free_ char *buf = NULL;
899         int r;
900
901         r = pretty_boot_time(bus, &buf);
902         if (r < 0)
903                 return r;
904
905         puts(buf);
906         return 0;
907 }
908
909 static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[]) {
910         _cleanup_strv_free_ char **units = NULL;
911         char **unit;
912         int r;
913
914         assert(u);
915         assert(prop);
916         assert(color);
917
918         r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
919         if (r < 0)
920                 return r;
921
922         STRV_FOREACH(unit, units) {
923                 char **p;
924                 bool match_found;
925
926                 if (!strv_isempty(arg_dot_from_patterns)) {
927                         match_found = false;
928
929                         STRV_FOREACH(p, arg_dot_from_patterns)
930                                 if (fnmatch(*p, u->id, 0) == 0) {
931                                         match_found = true;
932                                         break;
933                                 }
934
935                         if (!match_found)
936                                 continue;
937                 }
938
939                 if (!strv_isempty(arg_dot_to_patterns)) {
940                         match_found = false;
941
942                         STRV_FOREACH(p, arg_dot_to_patterns)
943                                 if (fnmatch(*p, *unit, 0) == 0) {
944                                         match_found = true;
945                                         break;
946                                 }
947
948                         if (!match_found)
949                                 continue;
950                 }
951
952                 if (!strv_isempty(patterns)) {
953                         match_found = false;
954
955                         STRV_FOREACH(p, patterns)
956                                 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
957                                         match_found = true;
958                                         break;
959                                 }
960                         if (!match_found)
961                                 continue;
962                 }
963
964                 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
965         }
966
967         return 0;
968 }
969
970 static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[]) {
971         int r;
972
973         assert(bus);
974         assert(u);
975
976         if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
977                 r = graph_one_property(bus, u, "After", "green", patterns);
978                 if (r < 0)
979                         return r;
980         }
981
982         if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
983                 r = graph_one_property(bus, u, "Requires", "black", patterns);
984                 if (r < 0)
985                         return r;
986                 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
987                 if (r < 0)
988                         return r;
989                 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
990                 if (r < 0)
991                         return r;
992                 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
993                 if (r < 0)
994                         return r;
995                 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
996                 if (r < 0)
997                         return r;
998                 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
999                 if (r < 0)
1000                         return r;
1001         }
1002
1003         return 0;
1004 }
1005
1006 static int dot(sd_bus *bus, char* patterns[]) {
1007         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1008         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1009         int r;
1010         UnitInfo u;
1011
1012         r = sd_bus_call_method(
1013                         bus,
1014                        "org.freedesktop.systemd1",
1015                        "/org/freedesktop/systemd1",
1016                        "org.freedesktop.systemd1.Manager",
1017                        "ListUnits",
1018                        &error,
1019                        &reply,
1020                        "");
1021         if (r < 0) {
1022                 log_error("Failed to list units: %s", bus_error_message(&error, -r));
1023                 return r;
1024         }
1025
1026         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1027         if (r < 0)
1028                 return bus_log_parse_error(r);
1029
1030         printf("digraph systemd {\n");
1031
1032         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1033
1034                 r = graph_one(bus, &u, patterns);
1035                 if (r < 0)
1036                         return r;
1037         }
1038         if (r < 0)
1039                 return bus_log_parse_error(r);
1040
1041         printf("}\n");
1042
1043         log_info("   Color legend: black     = Requires\n"
1044                  "                 dark blue = Requisite\n"
1045                  "                 dark grey = Wants\n"
1046                  "                 red       = Conflicts\n"
1047                  "                 green     = After\n");
1048
1049         if (on_tty())
1050                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1051                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1052
1053         return 0;
1054 }
1055
1056 static int dump(sd_bus *bus, char **args) {
1057         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1058         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1059         const char *text = NULL;
1060         int r;
1061
1062         if (!strv_isempty(args)) {
1063                 log_error("Too many arguments.");
1064                 return -E2BIG;
1065         }
1066
1067         pager_open_if_enabled();
1068
1069         r = sd_bus_call_method(
1070                         bus,
1071                        "org.freedesktop.systemd1",
1072                        "/org/freedesktop/systemd1",
1073                        "org.freedesktop.systemd1.Manager",
1074                        "Dump",
1075                        &error,
1076                        &reply,
1077                        "");
1078         if (r < 0) {
1079                 log_error("Failed issue method call: %s", bus_error_message(&error, -r));
1080                 return r;
1081         }
1082
1083         r = sd_bus_message_read(reply, "s", &text);
1084         if (r < 0)
1085                 return bus_log_parse_error(r);
1086
1087         fputs(text, stdout);
1088         return 0;
1089 }
1090
1091 static int set_log_level(sd_bus *bus, char **args) {
1092         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1093         int r;
1094
1095         assert(bus);
1096         assert(args);
1097
1098         if (strv_length(args) != 1) {
1099                 log_error("This command expects one argument only.");
1100                 return -E2BIG;
1101         }
1102
1103         r = sd_bus_set_property(
1104                         bus,
1105                         "org.freedesktop.systemd1",
1106                         "/org/freedesktop/systemd1",
1107                         "org.freedesktop.systemd1.Manager",
1108                         "LogLevel",
1109                         &error,
1110                         "s",
1111                         args[0]);
1112         if (r < 0) {
1113                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1114                 return -EIO;
1115         }
1116
1117         return 0;
1118 }
1119
1120 static int help(void) {
1121
1122         pager_open_if_enabled();
1123
1124         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1125                "Process systemd profiling information.\n\n"
1126                "  -h --help               Show this help\n"
1127                "     --version            Show package version\n"
1128                "     --no-pager           Do not pipe output into a pager\n"
1129                "     --system             Connect to system manager\n"
1130                "     --user               Connect to user manager\n"
1131                "  -H --host=[USER@]HOST   Operate on remote host\n"
1132                "  -M --machine=CONTAINER  Operate on local container\n"
1133                "     --order              When generating a dependency graph, show only order\n"
1134                "     --require            When generating a dependency graph, show only requirement\n"
1135                "     --from-pattern=GLOB, --to-pattern=GLOB\n"
1136                "                          When generating a dependency graph, filter only origins\n"
1137                "                          or destinations, respectively\n"
1138                "     --fuzz=TIMESPAN      When printing the tree of the critical chain, print also\n"
1139                "                          services, which finished TIMESPAN earlier, than the\n"
1140                "                          latest in the branch. The unit of TIMESPAN is seconds\n"
1141                "                          unless specified with a different unit, i.e. 50ms\n\n"
1142                "Commands:\n"
1143                "  time                    Print time spent in the kernel before reaching userspace\n"
1144                "  blame                   Print list of running units ordered by time to init\n"
1145                "  critical-chain          Print a tree of the time critical chain of units\n"
1146                "  plot                    Output SVG graphic showing service initialization\n"
1147                "  dot                     Output dependency graph in dot(1) format\n"
1148                "  set-log-level LEVEL     Set logging threshold for systemd\n"
1149                "  dump                    Output state serialization of service manager\n",
1150                program_invocation_short_name);
1151
1152         /* When updating this list, including descriptions, apply
1153          * changes to shell-completion/bash/systemd and
1154          * shell-completion/systemd-zsh-completion.zsh too. */
1155
1156         return 0;
1157 }
1158
1159 static int parse_argv(int argc, char *argv[]) {
1160         enum {
1161                 ARG_VERSION = 0x100,
1162                 ARG_ORDER,
1163                 ARG_REQUIRE,
1164                 ARG_USER,
1165                 ARG_SYSTEM,
1166                 ARG_DOT_FROM_PATTERN,
1167                 ARG_DOT_TO_PATTERN,
1168                 ARG_FUZZ,
1169                 ARG_NO_PAGER
1170         };
1171
1172         static const struct option options[] = {
1173                 { "help",         no_argument,       NULL, 'h'                  },
1174                 { "version",      no_argument,       NULL, ARG_VERSION          },
1175                 { "order",        no_argument,       NULL, ARG_ORDER            },
1176                 { "require",      no_argument,       NULL, ARG_REQUIRE          },
1177                 { "user",         no_argument,       NULL, ARG_USER             },
1178                 { "system",       no_argument,       NULL, ARG_SYSTEM           },
1179                 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1180                 { "to-pattern",   required_argument, NULL, ARG_DOT_TO_PATTERN   },
1181                 { "fuzz",         required_argument, NULL, ARG_FUZZ             },
1182                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER         },
1183                 { "host",         required_argument, NULL, 'H'                  },
1184                 { "machine",      required_argument, NULL, 'M'                  },
1185                 {}
1186         };
1187
1188         int r, c;
1189
1190         assert(argc >= 0);
1191         assert(argv);
1192
1193         while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) {
1194
1195                 switch (c) {
1196
1197                 case 'h':
1198                         return help();
1199
1200                 case ARG_VERSION:
1201                         puts(PACKAGE_STRING);
1202                         puts(SYSTEMD_FEATURES);
1203                         return 0;
1204
1205                 case ARG_USER:
1206                         arg_user = true;
1207                         break;
1208
1209                 case ARG_SYSTEM:
1210                         arg_user = false;
1211                         break;
1212
1213                 case ARG_ORDER:
1214                         arg_dot = DEP_ORDER;
1215                         break;
1216
1217                 case ARG_REQUIRE:
1218                         arg_dot = DEP_REQUIRE;
1219                         break;
1220
1221                 case ARG_DOT_FROM_PATTERN:
1222                         if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1223                                 return log_oom();
1224
1225                         break;
1226
1227                 case ARG_DOT_TO_PATTERN:
1228                         if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1229                                 return log_oom();
1230
1231                         break;
1232
1233                 case ARG_FUZZ:
1234                         r = parse_sec(optarg, &arg_fuzz);
1235                         if (r < 0)
1236                                 return r;
1237                         break;
1238
1239                 case ARG_NO_PAGER:
1240                         arg_no_pager = true;
1241                         break;
1242
1243                 case 'H':
1244                         arg_transport = BUS_TRANSPORT_REMOTE;
1245                         arg_host = optarg;
1246                         break;
1247
1248                 case 'M':
1249                         arg_transport = BUS_TRANSPORT_CONTAINER;
1250                         arg_host = optarg;
1251                         break;
1252
1253                 case '?':
1254                         return -EINVAL;
1255
1256                 default:
1257                         assert_not_reached("Unhandled option");
1258                 }
1259         }
1260
1261         return 1;
1262 }
1263
1264 int main(int argc, char *argv[]) {
1265         _cleanup_bus_unref_ sd_bus *bus = NULL;
1266         int r;
1267
1268         setlocale(LC_ALL, "");
1269         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1270         log_parse_environment();
1271         log_open();
1272
1273         r = parse_argv(argc, argv);
1274         if (r <= 0)
1275                 goto finish;
1276
1277         r = bus_open_transport(arg_transport, arg_host, arg_user, &bus);
1278         if (r < 0) {
1279                 log_error("Failed to create bus connection: %s", strerror(-r));
1280                 goto finish;
1281         }
1282
1283         if (!argv[optind] || streq(argv[optind], "time"))
1284                 r = analyze_time(bus);
1285         else if (streq(argv[optind], "blame"))
1286                 r = analyze_blame(bus);
1287         else if (streq(argv[optind], "critical-chain"))
1288                 r = analyze_critical_chain(bus, argv+optind+1);
1289         else if (streq(argv[optind], "plot"))
1290                 r = analyze_plot(bus);
1291         else if (streq(argv[optind], "dot"))
1292                 r = dot(bus, argv+optind+1);
1293         else if (streq(argv[optind], "dump"))
1294                 r = dump(bus, argv+optind+1);
1295         else if (streq(argv[optind], "set-log-level"))
1296                 r = set_log_level(bus, argv+optind+1);
1297         else
1298                 log_error("Unknown operation '%s'.", argv[optind]);
1299
1300 finish:
1301         pager_close();
1302
1303         strv_free(arg_dot_from_patterns);
1304         strv_free(arg_dot_to_patterns);
1305
1306         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1307 }