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