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