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