]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/analyze/systemd-analyze.c
analyze: fix OOM handling + fix memory leak
[thirdparty/systemd.git] / src / analyze / systemd-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 "install.h"
31#include "log.h"
32#include "dbus-common.h"
33#include "build.h"
34#include "util.h"
35#include "strxcpyx.h"
36#include "fileio.h"
37#include "strv.h"
38
39#define SCALE_X (0.1 / 1000.0) /* pixels per us */
40#define SCALE_Y 20.0
41
42#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
43
44#define svg(...) printf(__VA_ARGS__)
45
46#define svg_bar(class, x1, x2, y) \
47 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
48 (class), \
49 SCALE_X * (x1), SCALE_Y * (y), \
50 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
51
52#define svg_text(b, x, y, format, ...) \
53 do { \
54 svg(" <text class=\"%s\" x=\"%.03f\" y=\"%.03f\">", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \
55 svg(format, ## __VA_ARGS__); \
56 svg("</text>\n"); \
57 } while(false)
58
59static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
60static enum dot {
61 DEP_ALL,
62 DEP_ORDER,
63 DEP_REQUIRE
64} arg_dot = DEP_ALL;
65static char** arg_dot_from_patterns = NULL;
66static char** arg_dot_to_patterns = NULL;
67
68struct boot_times {
69 usec_t firmware_time;
70 usec_t loader_time;
71 usec_t kernel_time;
72 usec_t kernel_done_time;
73 usec_t initrd_time;
74 usec_t userspace_time;
75 usec_t finish_time;
76};
77struct unit_times {
78 char *name;
79 usec_t ixt;
80 usec_t iet;
81 usec_t axt;
82 usec_t aet;
83 usec_t time;
84};
85
86static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
87 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
88 DBusMessageIter iter, sub;
89 int r;
90
91 r = bus_method_call_with_reply(
92 bus,
93 "org.freedesktop.systemd1",
94 path,
95 "org.freedesktop.DBus.Properties",
96 "Get",
97 &reply,
98 NULL,
99 DBUS_TYPE_STRING, &interface,
100 DBUS_TYPE_STRING, &property,
101 DBUS_TYPE_INVALID);
102 if (r < 0)
103 return r;
104
105 if (!dbus_message_iter_init(reply, &iter) ||
106 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) {
107 log_error("Failed to parse reply.");
108 return -EIO;
109 }
110
111 dbus_message_iter_recurse(&iter, &sub);
112
113 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_UINT64) {
114 log_error("Failed to parse reply.");
115 return -EIO;
116 }
117
118 dbus_message_iter_get_basic(&sub, val);
119
120 return 0;
121}
122
123static int compare_unit_time(const void *a, const void *b) {
124 return compare(((struct unit_times *)b)->time,
125 ((struct unit_times *)a)->time);
126}
127
128static int compare_unit_start(const void *a, const void *b) {
129 return compare(((struct unit_times *)a)->ixt,
130 ((struct unit_times *)b)->ixt);
131}
132
133static int get_os_name(char **_n) {
134 char *n = NULL;
135 int r;
136
137 r = parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &n, NULL);
138 if (r < 0)
139 return r;
140
141 if (!n)
142 return -ENOENT;
143
144 *_n = n;
145 return 0;
146}
147
148static void free_unit_times(struct unit_times *t, unsigned n) {
149 struct unit_times *p;
150
151 for (p = t; p < t + n; p++)
152 free(p->name);
153
154 free(t);
155}
156
157static int acquire_time_data(DBusConnection *bus, struct unit_times **out) {
158 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
159 DBusMessageIter iter, sub;
160 int r, c = 0, n_units = 0;
161 struct unit_times *unit_times = NULL;
162
163 r = bus_method_call_with_reply(
164 bus,
165 "org.freedesktop.systemd1",
166 "/org/freedesktop/systemd1",
167 "org.freedesktop.systemd1.Manager",
168 "ListUnits",
169 &reply,
170 NULL,
171 DBUS_TYPE_INVALID);
172 if (r < 0)
173 goto fail;
174
175 if (!dbus_message_iter_init(reply, &iter) ||
176 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
177 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
178 log_error("Failed to parse reply.");
179 r = -EIO;
180 goto fail;
181 }
182
183 for (dbus_message_iter_recurse(&iter, &sub);
184 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
185 dbus_message_iter_next(&sub)) {
186 struct unit_info u;
187 struct unit_times *t;
188
189 if (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_STRUCT) {
190 log_error("Failed to parse reply.");
191 r = -EIO;
192 goto fail;
193 }
194
195 if (c >= n_units) {
196 struct unit_times *w;
197
198 n_units = MAX(2*c, 16);
199 w = realloc(unit_times, sizeof(struct unit_times) * n_units);
200
201 if (!w) {
202 r = log_oom();
203 goto fail;
204 }
205
206 unit_times = w;
207 }
208 t = unit_times+c;
209 t->name = NULL;
210
211 r = bus_parse_unit_info(&sub, &u);
212 if (r < 0)
213 goto fail;
214
215 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
216
217 if (bus_get_uint64_property(bus, u.unit_path,
218 "org.freedesktop.systemd1.Unit",
219 "InactiveExitTimestampMonotonic",
220 &t->ixt) < 0 ||
221 bus_get_uint64_property(bus, u.unit_path,
222 "org.freedesktop.systemd1.Unit",
223 "ActiveEnterTimestampMonotonic",
224 &t->aet) < 0 ||
225 bus_get_uint64_property(bus, u.unit_path,
226 "org.freedesktop.systemd1.Unit",
227 "ActiveExitTimestampMonotonic",
228 &t->axt) < 0 ||
229 bus_get_uint64_property(bus, u.unit_path,
230 "org.freedesktop.systemd1.Unit",
231 "InactiveEnterTimestampMonotonic",
232 &t->iet) < 0) {
233 r = -EIO;
234 goto fail;
235 }
236
237 if (t->aet >= t->ixt)
238 t->time = t->aet - t->ixt;
239 else if (t->iet >= t->ixt)
240 t->time = t->iet - t->ixt;
241 else
242 t->time = 0;
243
244 if (t->ixt == 0)
245 continue;
246
247 t->name = strdup(u.id);
248 if (t->name == NULL) {
249 r = log_oom();
250 goto fail;
251 }
252 c++;
253 }
254
255 *out = unit_times;
256 return c;
257
258fail:
259 free_unit_times(unit_times, (unsigned) c);
260 return r;
261}
262
263static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
264 static struct boot_times times;
265 static bool cached = false;
266
267 if (cached)
268 goto finish;
269
270 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
271
272 if (bus_get_uint64_property(bus,
273 "/org/freedesktop/systemd1",
274 "org.freedesktop.systemd1.Manager",
275 "FirmwareTimestampMonotonic",
276 &times.firmware_time) < 0 ||
277 bus_get_uint64_property(bus,
278 "/org/freedesktop/systemd1",
279 "org.freedesktop.systemd1.Manager",
280 "LoaderTimestampMonotonic",
281 &times.loader_time) < 0 ||
282 bus_get_uint64_property(bus,
283 "/org/freedesktop/systemd1",
284 "org.freedesktop.systemd1.Manager",
285 "KernelTimestamp",
286 &times.kernel_time) < 0 ||
287 bus_get_uint64_property(bus,
288 "/org/freedesktop/systemd1",
289 "org.freedesktop.systemd1.Manager",
290 "InitRDTimestampMonotonic",
291 &times.initrd_time) < 0 ||
292 bus_get_uint64_property(bus,
293 "/org/freedesktop/systemd1",
294 "org.freedesktop.systemd1.Manager",
295 "UserspaceTimestampMonotonic",
296 &times.userspace_time) < 0 ||
297 bus_get_uint64_property(bus,
298 "/org/freedesktop/systemd1",
299 "org.freedesktop.systemd1.Manager",
300 "FinishTimestampMonotonic",
301 &times.finish_time) < 0)
302 return -EIO;
303
304 if (times.finish_time <= 0) {
305 log_error("Bootup is not yet finished. Please try again later.");
306 return -EAGAIN;
307 }
308
309 if (times.initrd_time)
310 times.kernel_done_time = times.initrd_time;
311 else
312 times.kernel_done_time = times.userspace_time;
313
314 cached = true;
315
316finish:
317 *bt = &times;
318 return 0;
319}
320
321static int pretty_boot_time(DBusConnection *bus, char **_buf) {
322 char ts[FORMAT_TIMESPAN_MAX];
323 struct boot_times *t;
324 static char buf[4096];
325 size_t size;
326 char *ptr;
327 int r;
328
329 r = acquire_boot_times(bus, &t);
330 if (r < 0)
331 return r;
332
333 ptr = buf;
334 size = sizeof(buf);
335
336 size = strpcpyf(&ptr, size, "Startup finished in ");
337 if (t->firmware_time)
338 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
339 if (t->loader_time)
340 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
341 if (t->kernel_time)
342 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
343 if (t->initrd_time > 0)
344 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
345
346 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
347 if (t->kernel_time > 0)
348 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
349 else
350 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
351
352 ptr = strdup(buf);
353 if (!ptr)
354 return log_oom();
355
356 *_buf = ptr;
357 return 0;
358}
359
360static void svg_graph_box(double height, double begin, double end) {
361 long long i;
362
363 /* outside box, fill */
364 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
365 SCALE_X * (end - begin), SCALE_Y * height);
366
367 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
368 /* lines for each second */
369 if (i % 5000000 == 0)
370 svg(" <line class=\"sec5\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
371 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
372 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
373 else if (i % 1000000 == 0)
374 svg(" <line class=\"sec1\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n"
375 " <text class=\"sec\" x=\"%.03f\" y=\"%.03f\" >%.01fs</text>\n",
376 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
377 else
378 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
379 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
380 }
381}
382
383static int analyze_plot(DBusConnection *bus) {
384 struct unit_times *times;
385 struct boot_times *boot;
386 struct utsname name;
387 int n, m = 1, y=0;
388 double width;
389 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
390 struct unit_times *u;
391
392 n = acquire_boot_times(bus, &boot);
393 if (n < 0)
394 return n;
395
396 n = pretty_boot_time(bus, &pretty_times);
397 if (n < 0)
398 return n;
399
400 get_os_name(&osname);
401 assert_se(uname(&name) >= 0);
402
403 n = acquire_time_data(bus, &times);
404 if (n <= 0)
405 return n;
406
407 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
408
409 width = SCALE_X * (boot->firmware_time + boot->finish_time);
410 if (width < 800.0)
411 width = 800.0;
412
413 if (boot->firmware_time > boot->loader_time)
414 m++;
415 if (boot->loader_time) {
416 m++;
417 if (width < 1000.0)
418 width = 1000.0;
419 }
420 if (boot->initrd_time)
421 m++;
422 if (boot->kernel_time)
423 m++;
424
425 for (u = times; u < times + n; u++) {
426 double len;
427
428 if (u->ixt < boot->userspace_time ||
429 u->ixt > boot->finish_time) {
430 free(u->name);
431 u->name = NULL;
432 continue;
433 }
434 len = ((boot->firmware_time + u->ixt) * SCALE_X)
435 + (10.0 * strlen(u->name));
436 if (len > width)
437 width = len;
438
439 if (u->iet > u->ixt && u->iet <= boot->finish_time
440 && u->aet == 0 && u->axt == 0)
441 u->aet = u->axt = u->iet;
442 if (u->aet < u->ixt || u->aet > boot->finish_time)
443 u->aet = boot->finish_time;
444 if (u->axt < u->aet || u->aet > boot->finish_time)
445 u->axt = boot->finish_time;
446 if (u->iet < u->axt || u->iet > boot->finish_time)
447 u->iet = boot->finish_time;
448 m++;
449 }
450
451 svg("<?xml version=\"1.0\" standalone=\"no\"?>\n"
452 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
453 "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
454
455 svg("<svg width=\"%.0fpx\" height=\"%.0fpx\" version=\"1.1\" "
456 "xmlns=\"http://www.w3.org/2000/svg\">\n\n",
457 80.0 + width, 150.0 + (m * SCALE_Y));
458
459 /* write some basic info as a comment, including some help */
460 svg("<!-- This file is a systemd-analyze SVG file. It is best rendered in a -->\n"
461 "<!-- browser such as Chrome, Chromium or Firefox. Other applications -->\n"
462 "<!-- that render these files properly but much slower are ImageMagick, -->\n"
463 "<!-- gimp, inkscape, etc. To display the files on your system, just -->\n"
464 "<!-- point your browser to this file. -->\n\n"
465 "<!-- This plot was generated by systemd-analyze version %-16.16s -->\n\n", VERSION);
466
467 /* style sheet */
468 svg("<defs>\n <style type=\"text/css\">\n <![CDATA[\n"
469 " rect { stroke-width: 1; stroke-opacity: 0; }\n"
470 " rect.activating { fill: rgb(255,0,0); fill-opacity: 0.7; }\n"
471 " rect.active { fill: rgb(200,150,150); fill-opacity: 0.7; }\n"
472 " rect.deactivating { fill: rgb(150,100,100); fill-opacity: 0.7; }\n"
473 " rect.kernel { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
474 " rect.initrd { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
475 " rect.firmware { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
476 " rect.loader { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
477 " rect.userspace { fill: rgb(150,150,150); fill-opacity: 0.7; }\n"
478 " rect.box { fill: rgb(240,240,240); stroke: rgb(192,192,192); }\n"
479 " line { stroke: rgb(64,64,64); stroke-width: 1; }\n"
480 "// line.sec1 { }\n"
481 " line.sec5 { stroke-width: 2; }\n"
482 " line.sec01 { stroke: rgb(224,224,224); stroke-width: 1; }\n"
483 " text { font-family: Verdana, Helvetica; font-size: 10; }\n"
484 " text.left { font-family: Verdana, Helvetica; font-size: 10; text-anchor: start; }\n"
485 " text.right { font-family: Verdana, Helvetica; font-size: 10; text-anchor: end; }\n"
486 " text.sec { font-size: 8; }\n"
487 " ]]>\n </style>\n</defs>\n\n");
488
489 svg("<text x=\"20\" y=\"50\">%s</text>", pretty_times);
490 svg("<text x=\"20\" y=\"30\">%s %s (%s %s) %s</text>",
491 isempty(osname) ? "Linux" : osname,
492 name.nodename, name.release, name.version, name.machine);
493 svg("<text x=\"20\" y=\"%.0f\">Legend: Red = Activating; Pink = Active; Dark Pink = Deactivating</text>",
494 120.0 + (m *SCALE_Y));
495
496 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
497 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
498
499 if (boot->firmware_time) {
500 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
501 svg_text(true, -(double) boot->firmware_time, y, "firmware");
502 y++;
503 }
504 if (boot->loader_time) {
505 svg_bar("loader", -(double) boot->loader_time, 0, y);
506 svg_text(true, -(double) boot->loader_time, y, "loader");
507 y++;
508 }
509 if (boot->kernel_time) {
510 svg_bar("kernel", 0, boot->kernel_done_time, y);
511 svg_text(true, 0, y, "kernel");
512 y++;
513 }
514 if (boot->initrd_time) {
515 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
516 svg_text(true, boot->initrd_time, y, "initrd");
517 y++;
518 }
519 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
520 svg_text("left", boot->userspace_time, y, "userspace");
521 y++;
522
523 for (u = times; u < times + n; u++) {
524 char ts[FORMAT_TIMESPAN_MAX];
525 bool b;
526
527 if (!u->name)
528 continue;
529
530 svg_bar("activating", u->ixt, u->aet, y);
531 svg_bar("active", u->aet, u->axt, y);
532 svg_bar("deactivating", u->axt, u->iet, y);
533
534 b = u->ixt * SCALE_X > width * 2 / 3;
535 if (u->time)
536 svg_text(b, u->ixt, y, "%s (%s)",
537 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
538 else
539 svg_text(b, u->ixt, y, "%s", u->name);
540 y++;
541 }
542 svg("</g>\n\n");
543
544 svg("</svg>");
545
546 free_unit_times(times, (unsigned) n);
547
548 return 0;
549}
550
551static int analyze_blame(DBusConnection *bus) {
552 struct unit_times *times;
553 unsigned i;
554 int n;
555
556 n = acquire_time_data(bus, &times);
557 if (n <= 0)
558 return n;
559
560 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
561
562 for (i = 0; i < (unsigned) n; i++) {
563 char ts[FORMAT_TIMESPAN_MAX];
564
565 if (times[i].time > 0)
566 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
567 }
568
569 free_unit_times(times, (unsigned) n);
570 return 0;
571}
572
573static int analyze_time(DBusConnection *bus) {
574 _cleanup_free_ char *buf = NULL;
575 int r;
576
577 r = pretty_boot_time(bus, &buf);
578 if (r < 0)
579 return r;
580
581 puts(buf);
582 return 0;
583}
584
585static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
586
587 static const char * const colors[] = {
588 "Requires", "[color=\"black\"]",
589 "RequiresOverridable", "[color=\"black\"]",
590 "Requisite", "[color=\"darkblue\"]",
591 "RequisiteOverridable", "[color=\"darkblue\"]",
592 "Wants", "[color=\"grey66\"]",
593 "Conflicts", "[color=\"red\"]",
594 "ConflictedBy", "[color=\"red\"]",
595 "After", "[color=\"green\"]"
596 };
597
598 const char *c = NULL;
599 unsigned i;
600
601 assert(name);
602 assert(prop);
603 assert(iter);
604
605 for (i = 0; i < ELEMENTSOF(colors); i += 2)
606 if (streq(colors[i], prop)) {
607 c = colors[i+1];
608 break;
609 }
610
611 if (!c)
612 return 0;
613
614 if (arg_dot != DEP_ALL)
615 if ((arg_dot == DEP_ORDER) != streq(prop, "After"))
616 return 0;
617
618 if (dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY &&
619 dbus_message_iter_get_element_type(iter) == DBUS_TYPE_STRING) {
620 DBusMessageIter sub;
621
622 dbus_message_iter_recurse(iter, &sub);
623
624 for (dbus_message_iter_recurse(iter, &sub);
625 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
626 dbus_message_iter_next(&sub)) {
627 const char *s;
628 char **p;
629 bool match_found = true;
630
631 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
632 dbus_message_iter_get_basic(&sub, &s);
633
634 STRV_FOREACH(p, arg_dot_from_patterns) {
635 match_found = false;
636 if (fnmatch(*p, name, 0) == 0) {
637 match_found = true;
638 break;
639 }
640 }
641 if (!match_found)
642 continue;
643
644 STRV_FOREACH(p, arg_dot_to_patterns) {
645 match_found = false;
646 if (fnmatch(*p, s, 0) == 0) {
647 match_found = true;
648 break;
649 }
650 }
651 if (!match_found)
652 continue;
653
654 STRV_FOREACH(p, patterns) {
655 match_found = false;
656 if (fnmatch(*p, name, 0) == 0 || fnmatch(*p, s, 0) == 0) {
657 match_found = true;
658 break;
659 }
660 }
661 if (!match_found)
662 continue;
663
664 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
665 }
666 }
667
668 return 0;
669}
670
671static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
672 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
673 const char *interface = "org.freedesktop.systemd1.Unit";
674 int r;
675 DBusMessageIter iter, sub, sub2, sub3;
676
677 assert(bus);
678 assert(u);
679
680 r = bus_method_call_with_reply(
681 bus,
682 "org.freedesktop.systemd1",
683 u->unit_path,
684 "org.freedesktop.DBus.Properties",
685 "GetAll",
686 &reply,
687 NULL,
688 DBUS_TYPE_STRING, &interface,
689 DBUS_TYPE_INVALID);
690 if (r < 0)
691 return r;
692
693 if (!dbus_message_iter_init(reply, &iter) ||
694 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
695 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_DICT_ENTRY) {
696 log_error("Failed to parse reply.");
697 return -EIO;
698 }
699
700 for (dbus_message_iter_recurse(&iter, &sub);
701 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
702 dbus_message_iter_next(&sub)) {
703 const char *prop;
704
705 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_DICT_ENTRY);
706 dbus_message_iter_recurse(&sub, &sub2);
707
708 if (bus_iter_get_basic_and_next(&sub2, DBUS_TYPE_STRING, &prop, true) < 0 ||
709 dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_VARIANT) {
710 log_error("Failed to parse reply.");
711 return -EIO;
712 }
713
714 dbus_message_iter_recurse(&sub2, &sub3);
715 r = graph_one_property(u->id, prop, &sub3, patterns);
716 if (r < 0)
717 return r;
718 }
719
720 return 0;
721}
722
723static int dot(DBusConnection *bus, char* patterns[]) {
724 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
725 DBusMessageIter iter, sub;
726 int r;
727
728 r = bus_method_call_with_reply(
729 bus,
730 "org.freedesktop.systemd1",
731 "/org/freedesktop/systemd1",
732 "org.freedesktop.systemd1.Manager",
733 "ListUnits",
734 &reply,
735 NULL,
736 DBUS_TYPE_INVALID);
737 if (r < 0)
738 return r;
739
740 if (!dbus_message_iter_init(reply, &iter) ||
741 dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY ||
742 dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_STRUCT) {
743 log_error("Failed to parse reply.");
744 return -EIO;
745 }
746
747 printf("digraph systemd {\n");
748
749 for (dbus_message_iter_recurse(&iter, &sub);
750 dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID;
751 dbus_message_iter_next(&sub)) {
752 struct unit_info u;
753
754 r = bus_parse_unit_info(&sub, &u);
755 if (r < 0)
756 return -EIO;
757
758 r = graph_one(bus, &u, patterns);
759 if (r < 0)
760 return r;
761 }
762
763 printf("}\n");
764
765 log_info(" Color legend: black = Requires\n"
766 " dark blue = Requisite\n"
767 " dark grey = Wants\n"
768 " red = Conflicts\n"
769 " green = After\n");
770
771 if (on_tty())
772 log_notice("-- You probably want to process this output with graphviz' dot tool.\n"
773 "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n");
774
775 return 0;
776}
777
778static void analyze_help(void)
779{
780 printf("%s [OPTIONS...] {COMMAND} ...\n\n"
781 "Process systemd profiling information\n\n"
782 " -h --help Show this help\n"
783 " --version Show package version\n"
784 " --system Connect to system manager\n"
785 " --user Connect to user service manager\n"
786 " --order When generating a dependency graph, show only order\n"
787 " --require When generating a dependency graph, show only requirement\n"
788 " --from-pattern=GLOB, --to-pattern=GLOB\n"
789 " When generating a dependency graph, filter only origins\n"
790 " or destinations, respectively\n\n"
791 "Commands:\n"
792 " time Print time spent in the kernel before reaching userspace\n"
793 " blame Print list of running units ordered by time to init\n"
794 " plot Output SVG graphic showing service initialization\n"
795 " dot Dump dependency graph (in dot(1) format)\n\n",
796 program_invocation_short_name);
797}
798
799static int parse_argv(int argc, char *argv[])
800{
801 enum {
802 ARG_VERSION = 0x100,
803 ARG_ORDER,
804 ARG_REQUIRE,
805 ARG_USER,
806 ARG_SYSTEM,
807 ARG_DOT_FROM_PATTERN,
808 ARG_DOT_TO_PATTERN
809 };
810
811 static const struct option options[] = {
812 { "help", no_argument, NULL, 'h' },
813 { "version", no_argument, NULL, ARG_VERSION },
814 { "order", no_argument, NULL, ARG_ORDER },
815 { "require", no_argument, NULL, ARG_REQUIRE },
816 { "user", no_argument, NULL, ARG_USER },
817 { "system", no_argument, NULL, ARG_SYSTEM },
818 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
819 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
820 { NULL, 0, NULL, 0 }
821 };
822
823 assert(argc >= 0);
824 assert(argv);
825
826 for (;;) {
827 switch (getopt_long(argc, argv, "h", options, NULL)) {
828
829 case 'h':
830 analyze_help();
831 return 0;
832
833 case ARG_VERSION:
834 puts(PACKAGE_STRING "\n" SYSTEMD_FEATURES);
835 return 0;
836
837 case ARG_USER:
838 arg_scope = UNIT_FILE_USER;
839 break;
840
841 case ARG_SYSTEM:
842 arg_scope = UNIT_FILE_SYSTEM;
843 break;
844
845 case ARG_ORDER:
846 arg_dot = DEP_ORDER;
847 break;
848
849 case ARG_REQUIRE:
850 arg_dot = DEP_REQUIRE;
851 break;
852
853 case ARG_DOT_FROM_PATTERN:
854 if (strv_extend(&arg_dot_from_patterns, optarg) < 0)
855 return log_oom();
856
857 break;
858
859 case ARG_DOT_TO_PATTERN:
860 if (strv_extend(&arg_dot_to_patterns, optarg) < 0)
861 return log_oom();
862
863 break;
864
865 case -1:
866 return 1;
867
868 case '?':
869 return -EINVAL;
870
871 default:
872 assert_not_reached("Unhandled option");
873 }
874 }
875}
876
877int main(int argc, char *argv[]) {
878 int r;
879 DBusConnection *bus = NULL;
880
881 setlocale(LC_ALL, "");
882 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
883 log_parse_environment();
884 log_open();
885
886 r = parse_argv(argc, argv);
887 if (r < 0)
888 return EXIT_FAILURE;
889 else if (r <= 0)
890 return EXIT_SUCCESS;
891
892 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
893 if (!bus)
894 return EXIT_FAILURE;
895
896 if (!argv[optind] || streq(argv[optind], "time"))
897 r = analyze_time(bus);
898 else if (streq(argv[optind], "blame"))
899 r = analyze_blame(bus);
900 else if (streq(argv[optind], "plot"))
901 r = analyze_plot(bus);
902 else if (streq(argv[optind], "dot"))
903 r = dot(bus, argv+optind+1);
904 else
905 log_error("Unknown operation '%s'.", argv[optind]);
906
907 strv_free(arg_dot_from_patterns);
908 strv_free(arg_dot_to_patterns);
909 dbus_connection_unref(bus);
910
911 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
912}