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