]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze.c
analyze: simplify things
[thirdparty/systemd.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 STRV_FOREACH(unit, units) {
957 char **p;
958 bool match_found;
959
960 if (!strv_isempty(arg_dot_from_patterns)) {
961 match_found = false;
962
963 STRV_FOREACH(p, arg_dot_from_patterns)
964 if (fnmatch(*p, u->id, 0) == 0) {
965 match_found = true;
966 break;
967 }
968
969 if (!match_found)
970 continue;
971 }
972
973 if (!strv_isempty(arg_dot_to_patterns)) {
974 match_found = false;
975
976 STRV_FOREACH(p, arg_dot_to_patterns)
977 if (fnmatch(*p, *unit, 0) == 0) {
978 match_found = true;
979 break;
980 }
981
982 if (!match_found)
983 continue;
984 }
985
986 if (!strv_isempty(patterns)) {
987 match_found = false;
988
989 STRV_FOREACH(p, patterns)
990 if (fnmatch(*p, u->id, 0) == 0 || fnmatch(*p, *unit, 0) == 0) {
991 match_found = true;
992 break;
993 }
994 if (!match_found)
995 continue;
996 }
997
998 printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color);
999 }
1000
1001 return 0;
1002 }
1003
1004 static int graph_one(sd_bus *bus, const struct unit_info *u, char *patterns[]) {
1005 int r;
1006
1007 assert(bus);
1008 assert(u);
1009
1010 if (arg_dot == DEP_ORDER ||arg_dot == DEP_ALL) {
1011 r = graph_one_property(bus, u, "After", "green", patterns);
1012 if (r < 0)
1013 return r;
1014 }
1015
1016 if (arg_dot == DEP_REQUIRE ||arg_dot == DEP_ALL) {
1017 r = graph_one_property(bus, u, "Requires", "black", patterns);
1018 if (r < 0)
1019 return r;
1020 r = graph_one_property(bus, u, "RequiresOverridable", "black", patterns);
1021 if (r < 0)
1022 return r;
1023 r = graph_one_property(bus, u, "RequisiteOverridable", "darkblue", patterns);
1024 if (r < 0)
1025 return r;
1026 r = graph_one_property(bus, u, "Wants", "grey66", patterns);
1027 if (r < 0)
1028 return r;
1029 r = graph_one_property(bus, u, "Conflicts", "red", patterns);
1030 if (r < 0)
1031 return r;
1032 r = graph_one_property(bus, u, "ConflictedBy", "red", patterns);
1033 if (r < 0)
1034 return r;
1035 }
1036
1037 return 0;
1038 }
1039
1040 static int dot(sd_bus *bus, char* patterns[]) {
1041 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1042 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1043 int r;
1044 struct unit_info u;
1045
1046 r = sd_bus_call_method(
1047 bus,
1048 "org.freedesktop.systemd1",
1049 "/org/freedesktop/systemd1",
1050 "org.freedesktop.systemd1.Manager",
1051 "ListUnits",
1052 &error,
1053 &reply,
1054 "");
1055 if (r < 0) {
1056 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1057 return r;
1058 }
1059
1060 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
1061 if (r < 0)
1062 return r;
1063
1064 printf("digraph systemd {\n");
1065
1066 while ((r = bus_parse_unit_info(reply, &u)) > 0) {
1067 r = graph_one(bus, &u, patterns);
1068 if (r < 0)
1069 return r;
1070 }
1071
1072 printf("}\n");
1073
1074 log_info(" Color legend: black = Requires\n"
1075 " dark blue = Requisite\n"
1076 " dark grey = Wants\n"
1077 " red = Conflicts\n"
1078 " green = After\n");
1079
1080 if (on_tty())
1081 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
1082 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
1083
1084 return 0;
1085 }
1086 static int dump(sd_bus *bus, char **args) {
1087 _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
1088 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1089 const char *text = NULL;
1090 int r;
1091
1092 if (!strv_isempty(args)) {
1093 log_error("Too many arguments.");
1094 return -E2BIG;
1095 }
1096
1097 pager_open_if_enabled();
1098
1099 r = sd_bus_call_method(
1100 bus,
1101 "org.freedesktop.systemd1",
1102 "/org/freedesktop/systemd1",
1103 "org.freedesktop.systemd1.Manager",
1104 "Dump",
1105 &error,
1106 &reply,
1107 "");
1108 if (r < 0) {
1109 log_error("Failed to parse reply: %s", bus_error_message(&error, -r));
1110 return r;
1111 }
1112
1113 r = sd_bus_message_read(reply, "s", &text);
1114 if (r < 0) {
1115 log_error("Failed to parse reply");
1116 return r;
1117 }
1118
1119 fputs(text, stdout);
1120 return 0;
1121 }
1122
1123 static int set_log_level(sd_bus *bus, char **args) {
1124 _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
1125 int r;
1126
1127 assert(bus);
1128 assert(args);
1129
1130 if (strv_length(args) != 1) {
1131 log_error("This command expects one argument only.");
1132 return -E2BIG;
1133 }
1134
1135 r = sd_bus_set_property(
1136 bus,
1137 "org.freedesktop.systemd1",
1138 "/org/freedesktop/systemd1",
1139 "org.freedesktop.systemd1.Manager",
1140 "LogLevel",
1141 &error,
1142 "s",
1143 args[0]);
1144 if (r < 0) {
1145 log_error("Failed to issue method call: %s", bus_error_message(&error, -r));
1146 return -EIO;
1147 }
1148
1149 return 0;
1150 }
1151
1152 static void analyze_help(void) {
1153
1154 pager_open_if_enabled();
1155
1156 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
1157 "Process systemd profiling information\n\n"
1158 " -h --help Show this help\n"
1159 " --version Show package version\n"
1160 " --system Connect to system manager\n"
1161 " --user Connect to user service manager\n"
1162 " --order When generating a dependency graph, show only order\n"
1163 " --require When generating a dependency graph, show only requirement\n"
1164 " --from-pattern=GLOB, --to-pattern=GLOB\n"
1165 " When generating a dependency graph, filter only origins\n"
1166 " or destinations, respectively\n"
1167 " --fuzz=TIMESPAN When printing the tree of the critical chain, print also\n"
1168 " services, which finished TIMESPAN earlier, than the\n"
1169 " latest in the branch. The unit of TIMESPAN is seconds\n"
1170 " unless specified with a different unit, i.e. 50ms\n"
1171 " --no-pager Do not pipe output into a pager\n\n"
1172 "Commands:\n"
1173 " time Print time spent in the kernel before reaching userspace\n"
1174 " blame Print list of running units ordered by time to init\n"
1175 " critical-chain Print a tree of the time critical chain of units\n"
1176 " plot Output SVG graphic showing service initialization\n"
1177 " dot Output dependency graph in dot(1) format\n"
1178 " set-log-level LEVEL Set logging threshold for systemd\n"
1179 " dump Output state serialization of service manager\n",
1180 program_invocation_short_name);
1181
1182 /* When updating this list, including descriptions, apply
1183 * changes to shell-completion/bash/systemd and
1184 * shell-completion/systemd-zsh-completion.zsh too. */
1185 }
1186
1187 static int parse_argv(int argc, char *argv[]) {
1188 int r;
1189
1190 enum {
1191 ARG_VERSION = 0x100,
1192 ARG_ORDER,
1193 ARG_REQUIRE,
1194 ARG_USER,
1195 ARG_SYSTEM,
1196 ARG_DOT_FROM_PATTERN,
1197 ARG_DOT_TO_PATTERN,
1198 ARG_FUZZ,
1199 ARG_NO_PAGER
1200 };
1201
1202 static const struct option options[] = {
1203 { "help", no_argument, NULL, 'h' },
1204 { "version", no_argument, NULL, ARG_VERSION },
1205 { "order", no_argument, NULL, ARG_ORDER },
1206 { "require", no_argument, NULL, ARG_REQUIRE },
1207 { "user", no_argument, NULL, ARG_USER },
1208 { "system", no_argument, NULL, ARG_SYSTEM },
1209 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN },
1210 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
1211 { "fuzz", required_argument, NULL, ARG_FUZZ },
1212 { "no-pager", no_argument, NULL, ARG_NO_PAGER },
1213 { NULL, 0, NULL, 0 }
1214 };
1215
1216 assert(argc >= 0);
1217 assert(argv);
1218
1219 for (;;) {
1220 switch (getopt_long(argc, argv, "h", options, NULL)) {
1221
1222 case 'h':
1223 analyze_help();
1224 return 0;
1225
1226 case ARG_VERSION:
1227 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
1228 return 0;
1229
1230 case ARG_USER:
1231 arg_scope = UNIT_FILE_USER;
1232 break;
1233
1234 case ARG_SYSTEM:
1235 arg_scope = UNIT_FILE_SYSTEM;
1236 break;
1237
1238 case ARG_ORDER:
1239 arg_dot = DEP_ORDER;
1240 break;
1241
1242 case ARG_REQUIRE:
1243 arg_dot = DEP_REQUIRE;
1244 break;
1245
1246 case ARG_DOT_FROM_PATTERN:
1247 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
1248 return log_oom();
1249
1250 break;
1251
1252 case ARG_DOT_TO_PATTERN:
1253 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
1254 return log_oom();
1255
1256 break;
1257
1258 case ARG_FUZZ:
1259 r = parse_sec(optarg, &arg_fuzz);
1260 if (r < 0)
1261 return r;
1262 break;
1263
1264 case ARG_NO_PAGER:
1265 arg_no_pager = true;
1266 break;
1267
1268 case -1:
1269 return 1;
1270
1271 case '?':
1272 return -EINVAL;
1273
1274 default:
1275 assert_not_reached("Unhandled option");
1276 }
1277 }
1278 }
1279
1280 int main(int argc, char *argv[]) {
1281 _cleanup_bus_unref_ sd_bus *bus = NULL;
1282 int r;
1283
1284 setlocale(LC_ALL, "");
1285 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
1286 log_parse_environment();
1287 log_open();
1288
1289 r = parse_argv(argc, argv);
1290 if (r <= 0)
1291 goto finish;
1292
1293 if (arg_scope == UNIT_FILE_SYSTEM)
1294 r = sd_bus_open_system(&bus);
1295 else
1296 r = sd_bus_open_user(&bus);
1297
1298 if (r < 0) {
1299 log_error("Failed to connect to bus: %s", strerror(-r));
1300 goto finish;
1301 }
1302
1303 if (!argv[optind] || streq(argv[optind], "time"))
1304 r = analyze_time(bus);
1305 else if (streq(argv[optind], "blame"))
1306 r = analyze_blame(bus);
1307 else if (streq(argv[optind], "critical-chain"))
1308 r = analyze_critical_chain(bus, argv+optind+1);
1309 else if (streq(argv[optind], "plot"))
1310 r = analyze_plot(bus);
1311 else if (streq(argv[optind], "dot"))
1312 r = dot(bus, argv+optind+1);
1313 else if (streq(argv[optind], "dump"))
1314 r = dump(bus, argv+optind+1);
1315 else if (streq(argv[optind], "set-log-level"))
1316 r = set_log_level(bus, argv+optind+1);
1317 else
1318 log_error("Unknown operation '%s'.", argv[optind]);
1319
1320 finish:
1321 pager_close();
1322
1323 strv_free(arg_dot_from_patterns);
1324 strv_free(arg_dot_to_patterns);
1325
1326 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
1327 }