chiark / gitweb /
6bfe13d82db2d15c5611e0b08e5fe55c46a73f45
[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 ixt;
106         usec_t iet;
107         usec_t axt;
108         usec_t aet;
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)->ixt,
148                        ((struct unit_times *)b)->ixt);
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->ixt) < 0 ||
287                     bus_get_uint64_property(bus, u.unit_path,
288                                             "org.freedesktop.systemd1.Unit",
289                                             "ActiveEnterTimestampMonotonic",
290                                             &t->aet) < 0 ||
291                     bus_get_uint64_property(bus, u.unit_path,
292                                             "org.freedesktop.systemd1.Unit",
293                                             "ActiveExitTimestampMonotonic",
294                                             &t->axt) < 0 ||
295                     bus_get_uint64_property(bus, u.unit_path,
296                                             "org.freedesktop.systemd1.Unit",
297                                             "InactiveEnterTimestampMonotonic",
298                                             &t->iet) < 0) {
299                         r = -EIO;
300                         goto fail;
301                 }
302
303                 if (t->aet >= t->ixt)
304                         t->time = t->aet - t->ixt;
305                 else if (t->iet >= t->ixt)
306                         t->time = t->iet - t->ixt;
307                 else
308                         t->time = 0;
309
310                 if (t->ixt == 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 len;
513
514                 if (u->ixt < boot->userspace_time ||
515                     u->ixt > boot->finish_time) {
516                         free(u->name);
517                         u->name = NULL;
518                         continue;
519                 }
520                 len = ((boot->firmware_time + u->ixt) * SCALE_X)
521                         + (10.0 * strlen(u->name));
522                 if (len > width)
523                         width = len;
524
525                 if (u->iet > u->ixt && u->iet <= boot->finish_time
526                                 && u->aet == 0 && u->axt == 0)
527                         u->aet = u->axt = u->iet;
528                 if (u->aet < u->ixt || u->aet > boot->finish_time)
529                         u->aet = boot->finish_time;
530                 if (u->axt < u->aet || u->aet > boot->finish_time)
531                         u->axt = boot->finish_time;
532                 if (u->iet < u->axt || u->iet > boot->finish_time)
533                         u->iet = boot->finish_time;
534                 m++;
535         }
536
537         svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
538             "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
539             "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
540
541         svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
542             "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
543                         80.0 + width, 150.0 + (m * SCALE_Y) +
544                         5 * SCALE_Y /* legend */);
545
546         /* write some basic info as a comment, including some help */
547         svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a   -->\n"
548             "<!-- browser such as Chrome, Chromium or Firefox. Other applications     -->\n"
549             "<!-- that render these files properly but much slower are ImageMagick,   -->\n"
550             "<!-- gimp, inkscape, etc. To display the files on your system, just      -->\n"
551             "<!-- point your browser to this file.                                    -->\n\n"
552             "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
553
554         /* style sheet */
555         svg("<defs>\n  <style type=\"text/css\">\n    <![CDATA[\n"
556             "      rect       { stroke-width: 1; stroke-opacity: 0; }\n"
557             "      rect.background   { fill: rgb(255,255,255); }\n"
558             "      rect.activating   { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
559             "      rect.active       { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
560             "      rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
561             "      rect.kernel       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
562             "      rect.initrd       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
563             "      rect.firmware     { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
564             "      rect.loader       { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
565             "      rect.userspace    { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
566             "      rect.generators   { fill: rgb(102,204,255); fill-opacity: 0.7; }\n"
567             "      rect.unitsload    { fill: rgb( 82,184,255); fill-opacity: 0.7; }\n"
568             "      rect.box   { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
569             "      line       { stroke: rgb(64,64,64); stroke-width: 1; }\n"
570             "//    line.sec1  { }\n"
571             "      line.sec5  { stroke-width: 2; }\n"
572             "      line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
573             "      text       { font-family: Verdana, Helvetica; font-size: 14px; }\n"
574             "      text.left  { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: start; }\n"
575             "      text.right { font-family: Verdana, Helvetica; font-size: 14px; text-anchor: end; }\n"
576             "      text.sec   { font-size: 10px; }\n"
577             "    ]]>\n   </style>\n</defs>\n\n");
578
579         svg("<rect class=\"background\" width=\"100%%\" height=\"100%%\" />\n");
580         svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
581         svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
582             isempty(osname) ? "Linux" : osname,
583             name.nodename, name.release, name.version, name.machine);
584
585         svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
586         svg_graph_box(m, -boot->firmware_time, boot->finish_time);
587
588         if (boot->firmware_time) {
589                 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
590                 svg_text(true, -(double) boot->firmware_time, y, "firmware");
591                 y++;
592         }
593         if (boot->loader_time) {
594                 svg_bar("loader", -(double) boot->loader_time, 0, y);
595                 svg_text(true, -(double) boot->loader_time, y, "loader");
596                 y++;
597         }
598         if (boot->kernel_time) {
599                 svg_bar("kernel", 0, boot->kernel_done_time, y);
600                 svg_text(true, 0, y, "kernel");
601                 y++;
602         }
603         if (boot->initrd_time) {
604                 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
605                 svg_text(true, boot->initrd_time, y, "initrd");
606                 y++;
607         }
608         svg_bar("active", boot->userspace_time, boot->finish_time, y);
609         svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y);
610         svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y);
611         svg_text("left", boot->userspace_time, y, "systemd");
612         y++;
613
614         for (u = times; u < times + n; u++) {
615                 char ts[FORMAT_TIMESPAN_MAX];
616                 bool b;
617
618                 if (!u->name)
619                         continue;
620
621                 svg_bar("activating",   u->ixt, u->aet, y);
622                 svg_bar("active",       u->aet, u->axt, y);
623                 svg_bar("deactivating", u->axt, u->iet, y);
624
625                 b = u->ixt * SCALE_X > width * 2 / 3;
626                 if (u->time)
627                         svg_text(b, u->ixt, y, "%s (%s)",
628                                  u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
629                 else
630                         svg_text(b, u->ixt, y, "%s", u->name);
631                 y++;
632         }
633
634         /* Legend */
635         y++;
636         svg_bar("activating", 0, 300000, y);
637         svg_text("right", 400000, y, "Activating");
638         y++;
639         svg_bar("active", 0, 300000, y);
640         svg_text("right", 400000, y, "Active");
641         y++;
642         svg_bar("deactivating", 0, 300000, y);
643         svg_text("right", 400000, y, "Deactivating");
644         y++;
645         svg_bar("generators", 0, 300000, y);
646         svg_text("right", 400000, y, "Generators");
647         y++;
648         svg_bar("unitsload", 0, 300000, y);
649         svg_text("right", 400000, y, "Loading unit files");
650         y++;
651
652         svg("</g>\n\n");
653
654         svg("</svg>");
655
656         free_unit_times(times, (unsigned) n);
657
658         return 0;
659 }
660
661 static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches,
662                                    bool last, struct unit_times *times, struct boot_times *boot) {
663         unsigned int i;
664         char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX];
665
666         for (i = level; i != 0; i--)
667                 printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERT : DRAW_TREE_SPACE));
668
669         printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH));
670
671         if (times) {
672                 if (times->time)
673                         printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED_ON, name,
674                                format_timespan(ts, sizeof(ts), times->ixt - boot->userspace_time, USEC_PER_MSEC),
675                                format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
676                 else if (times->aet > boot->userspace_time)
677                         printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
678                 else
679                         printf("%s", name);
680         } else printf("%s", name);
681         printf("\n");
682
683         return 0;
684 }
685
686 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
687         _cleanup_free_ char *path;
688
689         int r = 0;
690         char **ret = NULL;
691
692         assert(bus);
693         assert(name);
694         assert(deps);
695
696         path = unit_dbus_path_from_name(name);
697         if (path == NULL)
698                 return -EINVAL;
699
700         r = bus_get_unit_property_strv(bus, path, "After", &ret);
701
702         if (r < 0)
703                 strv_free(ret);
704         else
705                 *deps = ret;
706         return r;
707 }
708
709 static Hashmap *unit_times_hashmap;
710
711 static int list_dependencies_compare(const void *_a, const void *_b) {
712         const char **a = (const char**) _a, **b = (const char**) _b;
713         usec_t usa = 0, usb = 0;
714         struct unit_times *times;
715
716         times = hashmap_get(unit_times_hashmap, *a);
717         if (times)
718                 usa = times->aet;
719         times = hashmap_get(unit_times_hashmap, *b);
720         if (times)
721                 usb = times->aet;
722
723         return usb - usa;
724 }
725
726 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units,
727                                  unsigned int branches) {
728         _cleanup_strv_free_ char **deps = NULL;
729         char **c;
730         int r = 0;
731         usec_t service_longest = 0;
732         int to_print = 0;
733         struct unit_times *times;
734         struct boot_times *boot;
735
736         if(strv_extend(units, name))
737                 return log_oom();
738
739         r = list_dependencies_get_dependencies(bus, name, &deps);
740         if (r < 0)
741                 return r;
742
743         qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare);
744
745         r = acquire_boot_times(bus, &boot);
746         if (r < 0)
747                 return r;
748
749         STRV_FOREACH(c, deps) {
750                 times = hashmap_get(unit_times_hashmap, *c);
751                 if (times
752                     && times->aet
753                     && times->aet <= boot->finish_time
754                     && (times->aet >= service_longest
755                         || service_longest == 0)) {
756                         service_longest = times->aet;
757                         break;
758                 }
759         }
760
761         if (service_longest == 0 )
762                 return r;
763
764         STRV_FOREACH(c, deps) {
765                 times = hashmap_get(unit_times_hashmap, *c);
766                 if (times && times->aet
767                     && times->aet <= boot->finish_time
768                     && (service_longest - times->aet) <= arg_fuzz) {
769                         to_print++;
770                 }
771         }
772
773         if(!to_print)
774                 return r;
775
776         STRV_FOREACH(c, deps) {
777                 times = hashmap_get(unit_times_hashmap, *c);
778                 if (!times
779                     || !times->aet
780                     || times->aet > boot->finish_time
781                     || service_longest - times->aet > arg_fuzz)
782                         continue;
783
784                 to_print--;
785
786                 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
787                 if (r < 0)
788                         return r;
789
790                 if (strv_contains(*units, *c)) {
791                         r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
792                                                     true, NULL, boot);
793                         if (r < 0)
794                                 return r;
795                         continue;
796                 }
797
798                 r = list_dependencies_one(bus, *c, level + 1, units,
799                                           (branches << 1) | (to_print ? 1 : 0));
800                 if (r < 0)
801                         return r;
802
803                 if (!to_print)
804                         break;
805         }
806         return 0;
807 }
808
809 static int list_dependencies(sd_bus *bus, const char *name) {
810         _cleanup_strv_free_ char **units = NULL;
811         char ts[FORMAT_TIMESPAN_MAX];
812         struct unit_times *times;
813         int r;
814         const char *path, *id;
815         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
816         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
817         struct boot_times *boot;
818
819         assert(bus);
820
821         path = unit_dbus_path_from_name(name);
822         if (path == NULL)
823                 return -EINVAL;
824
825         r = sd_bus_get_property(
826                         bus,
827                         "org.freedesktop.systemd1",
828                         path,
829                         "org.freedesktop.systemd1.Unit",
830                         "Id",
831                         &error,
832                         &reply,
833                         "s");
834         if (r < 0) {
835                 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
836                 return r;
837         }
838
839         r = sd_bus_message_read(reply, "s", &id);
840         if (r < 0) {
841                 log_error("Failed to parse reply.");
842                 return r;
843         }
844
845         times = hashmap_get(unit_times_hashmap, id);
846
847         r = acquire_boot_times(bus, &boot);
848         if (r < 0)
849                 return r;
850
851         if (times) {
852                 if (times->time)
853                         printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED_ON, id,
854                                format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_HIGHLIGHT_OFF);
855                 else if (times->aet > boot->userspace_time)
856                         printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->aet - boot->userspace_time, USEC_PER_MSEC));
857                 else
858                         printf("%s\n", id);
859         }
860
861         return list_dependencies_one(bus, name, 0, &units, 0);
862 }
863
864 static int analyze_critical_chain(sd_bus *bus, char *names[]) {
865         struct unit_times *times;
866         int n, r;
867         unsigned int i;
868         Hashmap *h;
869
870         n = acquire_time_data(bus, &times);
871         if (n <= 0)
872                 return n;
873
874         h = hashmap_new(string_hash_func, string_compare_func);
875         if (!h)
876                 return -ENOMEM;
877
878         for (i = 0; i < (unsigned)n; i++) {
879                 r = hashmap_put(h, times[i].name, &times[i]);
880                 if (r < 0)
881                         return r;
882         }
883         unit_times_hashmap = h;
884
885         pager_open_if_enabled();
886
887         puts("The time after the unit is active or started is printed after the \"@\" character.\n"
888              "The time the unit takes to start is printed after the \"+\" character.\n");
889
890         if (!strv_isempty(names)) {
891                 char **name;
892                 STRV_FOREACH(name, names)
893                         list_dependencies(bus, *name);
894         } else
895                 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
896
897         hashmap_free(h);
898         free_unit_times(times, (unsigned) n);
899         return 0;
900 }
901
902 static int analyze_blame(sd_bus *bus) {
903         struct unit_times *times;
904         unsigned i;
905         int n;
906
907         n = acquire_time_data(bus, &times);
908         if (n <= 0)
909                 return n;
910
911         qsort(times, n, sizeof(struct unit_times), compare_unit_time);
912
913         pager_open_if_enabled();
914
915         for (i = 0; i < (unsigned) n; i++) {
916                 char ts[FORMAT_TIMESPAN_MAX];
917
918                 if (times[i].time > 0)
919                         printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
920         }
921
922         free_unit_times(times, (unsigned) n);
923         return 0;
924 }
925
926 static int analyze_time(sd_bus *bus) {
927         _cleanup_free_ char *buf = NULL;
928         int r;
929
930         r = pretty_boot_time(bus, &buf);
931         if (r < 0)
932                 return r;
933
934         puts(buf);
935         return 0;
936 }
937
938 static int graph_one_property(sd_bus *bus, const struct unit_info *u, const char* prop, const char *color, char* patterns[]) {
939         _cleanup_strv_free_ char **units = NULL;
940         char **unit;
941         int r;
942
943         assert(u);
944         assert(prop);
945         assert(color);
946
947         r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units);
948         if (r < 0) {
949             return -r;
950         }
951
952         STRV_FOREACH(unit, units) {
953                 char **p;
954                 bool match_found;
955
956                 if (!strv_isempty(arg_dot_from_patterns)) {
957                         match_found = false;
958
959                         STRV_FOREACH(p, arg_dot_from_patterns)
960                                 if (fnmatch(*p, u->id, 0) == 0) {
961                                         match_found = true;
962                                         break;
963                                 }
964
965                         if (!match_found)
966                                 continue;
967                 }
968
969                 if (!strv_isempty(arg_dot_to_patterns)) {
970                         match_found = false;
971
972                         STRV_FOREACH(p, arg_dot_to_patterns)
973                                 if (fnmatch(*p, *unit, 0) == 0) {
974                                         match_found = true;
975                                         break;
976                                 }
977
978                         if (!match_found)
979                                 continue;
980                 }
981
982                 if (!strv_isempty(patterns)) {
983                         match_found = false;
984
985                         STRV_FOREACH(p, patterns)
986                                 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
987                                         match_found = true;
988                                         break;
989                                 }
990                         if (!match_found)
991                                 continue;
992                 }
993
994                 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
995         }
996
997         return 0;
998 }
999
1000 static int graph_one(sd_bus *bus, const struct unit_info *u, char *patterns[]) {
1001         int r;
1002
1003         assert(bus);
1004         assert(u);
1005
1006         if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
1007                 r = graph_one_property(bus, u, "After", "green", patterns);
1008                 if (r < 0)
1009                         return r;
1010         }
1011
1012         if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
1013                 r = graph_one_property(bus, u, "Requires", "black", patterns);
1014                 if (r < 0)
1015                         return r;
1016                 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
1017                 if (r < 0)
1018                         return r;
1019                 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
1020                 if (r < 0)
1021                         return r;
1022                 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
1023                 if (r < 0)
1024                         return r;
1025                 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
1026                 if (r < 0)
1027                         return r;
1028                 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1029                 if (r < 0)
1030                         return r;
1031         }
1032
1033         return 0;
1034 }
1035
1036 static int dot(sd_bus *bus, char* patterns[]) {
1037         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1038         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1039         int r;
1040         struct unit_info u;
1041
1042         r = sd_bus_call_method(
1043                         bus,
1044                        "org.freedesktop.systemd1",
1045                        "/org/freedesktop/systemd1",
1046                        "org.freedesktop.systemd1.Manager",
1047                        "ListUnits",
1048                        &error,
1049                        &reply,
1050                        "");
1051         if (r < 0) {
1052             log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1053             return r;
1054         }
1055
1056         r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1057         if (r < 0)
1058             return r;
1059
1060         printf("digraph systemd {\n");
1061
1062         while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1063                 r = graph_one(bus, &u, patterns);
1064                 if (r < 0)
1065                         return r;
1066         }
1067
1068         printf("}\n");
1069
1070         log_info("   Color legend: black     = Requires\n"
1071                  "                 dark blue = Requisite\n"
1072                  "                 dark grey = Wants\n"
1073                  "                 red       = Conflicts\n"
1074                  "                 green     = After\n");
1075
1076         if (on_tty())
1077                 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1078                            "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1079
1080         return 0;
1081 }
1082 static int dump(sd_bus *bus, char **args) {
1083         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1084         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1085         const char *text = NULL;
1086         int r;
1087
1088         if (!strv_isempty(args)) {
1089                 log_error("Too many arguments.");
1090                 return -E2BIG;
1091         }
1092
1093         pager_open_if_enabled();
1094
1095         r = sd_bus_call_method(
1096                         bus,
1097                        "org.freedesktop.systemd1",
1098                        "/org/freedesktop/systemd1",
1099                        "org.freedesktop.systemd1.Manager",
1100                        "Dump",
1101                        &error,
1102                        &reply,
1103                        "");
1104         if (r < 0) {
1105             log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1106             return r;
1107         }
1108
1109         r = sd_bus_message_read(reply, "s", &text);
1110         if (r < 0) {
1111             log_error("Failed to parse reply");
1112             return r;
1113         }
1114
1115         fputs(text, stdout);
1116         return 0;
1117 }
1118
1119 static int set_log_level(sd_bus *bus, char **args) {
1120         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1121         int r;
1122         const char* value;
1123
1124         assert(bus);
1125         assert(args);
1126
1127         if (strv_length(args) != 1) {
1128                 log_error("This command expects one argument only.");
1129                 return -E2BIG;
1130         }
1131
1132         value = args[0];
1133
1134         r = sd_bus_set_property(
1135                         bus,
1136                         "org.freedesktop.systemd1",
1137                         "/org/freedesktop/systemd1",
1138                         "org.freedesktop.systemd1.Manager",
1139                         "LogLevel",
1140                         &error,
1141                         "s",
1142                         value);
1143         if (r < 0) {
1144                 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1145                 return -EIO;
1146         }
1147
1148         return 0;
1149 }
1150
1151 static void analyze_help(void) {
1152
1153         pager_open_if_enabled();
1154
1155         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1156                "Process systemd profiling information\n\n"
1157                "  -h --help           Show this help\n"
1158                "     --version        Show package version\n"
1159                "     --system         Connect to system manager\n"
1160                "     --user           Connect to user service manager\n"
1161                "     --order          When generating a dependency graph, show only order\n"
1162                "     --require        When generating a dependency graph, show only requirement\n"
1163                "     --from-pattern=GLOB, --to-pattern=GLOB\n"
1164                "                      When generating a dependency graph, filter only origins\n"
1165                "                      or destinations, respectively\n"
1166                "     --fuzz=TIMESPAN  When printing the tree of the critical chain, print also\n"
1167                "                      services, which finished TIMESPAN earlier, than the\n"
1168                "                      latest in the branch. The unit of TIMESPAN is seconds\n"
1169                "                      unless specified with a different unit, i.e. 50ms\n"
1170                "     --no-pager       Do not pipe output into a pager\n\n"
1171                "Commands:\n"
1172                "  time                Print time spent in the kernel before reaching userspace\n"
1173                "  blame               Print list of running units ordered by time to init\n"
1174                "  critical-chain      Print a tree of the time critical chain of units\n"
1175                "  plot                Output SVG graphic showing service initialization\n"
1176                "  dot                 Output dependency graph in dot(1) format\n"
1177                "  set-log-level LEVEL Set logging threshold for systemd\n"
1178                "  dump                Output state serialization of service manager\n",
1179                program_invocation_short_name);
1180
1181         /* When updating this list, including descriptions, apply
1182          * changes to shell-completion/bash/systemd and
1183          * shell-completion/systemd-zsh-completion.zsh too. */
1184 }
1185
1186 static int parse_argv(int argc, char *argv[]) {
1187         int r;
1188
1189         enum {
1190                 ARG_VERSION = 0x100,
1191                 ARG_ORDER,
1192                 ARG_REQUIRE,
1193                 ARG_USER,
1194                 ARG_SYSTEM,
1195                 ARG_DOT_FROM_PATTERN,
1196                 ARG_DOT_TO_PATTERN,
1197                 ARG_FUZZ,
1198                 ARG_NO_PAGER
1199         };
1200
1201         static const struct option options[] = {
1202                 { "help",         no_argument,       NULL, 'h'                  },
1203                 { "version",      no_argument,       NULL, ARG_VERSION          },
1204                 { "order",        no_argument,       NULL, ARG_ORDER            },
1205                 { "require",      no_argument,       NULL, ARG_REQUIRE          },
1206                 { "user",         no_argument,       NULL, ARG_USER             },
1207                 { "system",       no_argument,       NULL, ARG_SYSTEM           },
1208                 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1209                 { "to-pattern",   required_argument, NULL, ARG_DOT_TO_PATTERN   },
1210                 { "fuzz",         required_argument, NULL, ARG_FUZZ             },
1211                 { "no-pager",     no_argument,       NULL, ARG_NO_PAGER         },
1212                 { NULL,           0,                 NULL, 0                    }
1213         };
1214
1215         assert(argc >= 0);
1216         assert(argv);
1217
1218         for (;;) {
1219                 switch (getopt_long(argc, argv, "h", options, NULL)) {
1220
1221                 case 'h':
1222                         analyze_help();
1223                         return 0;
1224
1225                 case ARG_VERSION:
1226                         puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1227                         return 0;
1228
1229                 case ARG_USER:
1230                         arg_scope = UNIT_FILE_USER;
1231                         break;
1232
1233                 case ARG_SYSTEM:
1234                         arg_scope = UNIT_FILE_SYSTEM;
1235                         break;
1236
1237                 case ARG_ORDER:
1238                         arg_dot = DEP_ORDER;
1239                         break;
1240
1241                 case ARG_REQUIRE:
1242                         arg_dot = DEP_REQUIRE;
1243                         break;
1244
1245                 case ARG_DOT_FROM_PATTERN:
1246                         if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1247                                 return log_oom();
1248
1249                         break;
1250
1251                 case ARG_DOT_TO_PATTERN:
1252                         if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1253                                 return log_oom();
1254
1255                         break;
1256
1257                 case ARG_FUZZ:
1258                         r = parse_sec(optarg, &arg_fuzz);
1259                         if (r < 0)
1260                                 return r;
1261                         break;
1262
1263                 case ARG_NO_PAGER:
1264                         arg_no_pager = true;
1265                         break;
1266
1267                 case -1:
1268                         return 1;
1269
1270                 case '?':
1271                         return -EINVAL;
1272
1273                 default:
1274                         assert_not_reached("Unhandled option");
1275                 }
1276         }
1277 }
1278
1279 int main(int argc, char *argv[]) {
1280         _cleanup_bus_unref_ sd_bus *bus = NULL;
1281         int r;
1282
1283         setlocale(LC_ALL, "");
1284         setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1285         log_parse_environment();
1286         log_open();
1287
1288         r = parse_argv(argc, argv);
1289         if (r <= 0)
1290                 goto finish;
1291
1292         if (arg_scope == UNIT_FILE_SYSTEM)
1293                 r = sd_bus_open_system(&bus);
1294         else
1295                 r = sd_bus_open_user(&bus);
1296
1297         if (r < 0) {
1298                 log_error("Failed to connect to bus: %s", strerror(-r));
1299                 goto finish;
1300         }
1301
1302         if (!argv[optind] || streq(argv[optind], "time"))
1303                 r = analyze_time(bus);
1304         else if (streq(argv[optind], "blame"))
1305                 r = analyze_blame(bus);
1306         else if (streq(argv[optind], "critical-chain"))
1307                 r = analyze_critical_chain(bus, argv+optind+1);
1308         else if (streq(argv[optind], "plot"))
1309                 r = analyze_plot(bus);
1310         else if (streq(argv[optind], "dot"))
1311                 r = dot(bus, argv+optind+1);
1312         else if (streq(argv[optind], "dump"))
1313                 r = dump(bus, argv+optind+1);
1314         else if (streq(argv[optind], "set-log-level"))
1315                 r = set_log_level(bus, argv+optind+1);
1316         else
1317                 log_error("Unknown operation '%s'.", argv[optind]);
1318
1319 finish:
1320         pager_close();
1321
1322         strv_free(arg_dot_from_patterns);
1323         strv_free(arg_dot_to_patterns);
1324
1325         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1326 }