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