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