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