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