]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/analyze/systemd-analyze.c
analyze: add missing --help text entries
[thirdparty/systemd.git] / src / analyze / systemd-analyze.c
CommitLineData
2265fbf7
SP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
ffaa0e25
LP
6 Copyright 2010-2013 Lennart Poettering
7 Copyright 2013 Simon Peeters
2265fbf7
SP
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>
e55933db 28#include <fnmatch.h>
2265fbf7
SP
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"
a5c32cff 36#include "fileio.h"
e55933db 37#include "strv.h"
2265fbf7 38
2f6eb835
LP
39#define SCALE_X (0.1 / 1000.0) /* pixels per us */
40#define SCALE_Y 20.0
41
2265fbf7 42#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0))
c170f3a4 43
2265fbf7 44#define svg(...) printf(__VA_ARGS__)
c170f3a4
LP
45
46#define svg_bar(class, x1, x2, y) \
2265fbf7 47 svg(" <rect class=\"%s\" x=\"%.03f\" y=\"%.03f\" width=\"%.03f\" height=\"%.03f\" />\n", \
c170f3a4 48 (class), \
2f6eb835
LP
49 SCALE_X * (x1), SCALE_Y * (y), \
50 SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0)
c170f3a4
LP
51
52#define svg_text(b, x, y, format, ...) \
53 do { \
2f6eb835 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); \
c170f3a4
LP
55 svg(format, ## __VA_ARGS__); \
56 svg("</text>\n"); \
2265fbf7
SP
57 } while(false)
58
59static UnitFileScope arg_scope = UNIT_FILE_SYSTEM;
1700761b
SP
60static enum dot {
61 DEP_ALL,
62 DEP_ORDER,
63 DEP_REQUIRE
64} arg_dot = DEP_ALL;
e55933db
ŁS
65static char** arg_dot_from_patterns = NULL;
66static char** arg_dot_to_patterns = NULL;
2265fbf7 67
2265fbf7 68struct boot_times {
c170f3a4
LP
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;
2265fbf7
SP
76};
77struct unit_times {
78 char *name;
c170f3a4
LP
79 usec_t ixt;
80 usec_t iet;
81 usec_t axt;
82 usec_t aet;
83 usec_t time;
2265fbf7
SP
84};
85
c170f3a4 86static int bus_get_uint64_property(DBusConnection *bus, const char *path, const char *interface, const char *property, uint64_t *val) {
2265fbf7 87 _cleanup_dbus_message_unref_ DBusMessage *reply = NULL;
2265fbf7 88 DBusMessageIter iter, sub;
c170f3a4 89 int r;
2265fbf7 90
c170f3a4 91 r = bus_method_call_with_reply(
2265fbf7
SP
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
c170f3a4 123static int compare_unit_time(const void *a, const void *b) {
2265fbf7
SP
124 return compare(((struct unit_times *)b)->time,
125 ((struct unit_times *)a)->time);
126}
127
c170f3a4 128static int compare_unit_start(const void *a, const void *b) {
2265fbf7
SP
129 return compare(((struct unit_times *)a)->ixt,
130 ((struct unit_times *)b)->ixt);
131}
132
c170f3a4 133static int get_os_name(char **_n) {
2265fbf7 134 char *n = NULL;
c170f3a4
LP
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;
2265fbf7 143
c170f3a4
LP
144 *_n = n;
145 return 0;
2265fbf7
SP
146}
147
c170f3a4
LP
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) {
2265fbf7
SP
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
c170f3a4 163 r = bus_method_call_with_reply(
2265fbf7
SP
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);
c170f3a4 172 if (r < 0)
2265fbf7
SP
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
c170f3a4
LP
215 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
216
2265fbf7
SP
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
2265fbf7
SP
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;
c170f3a4 257
2265fbf7 258fail:
c170f3a4 259 free_unit_times(unit_times, (unsigned) c);
2265fbf7
SP
260 return r;
261}
262
c170f3a4 263static int acquire_boot_times(DBusConnection *bus, struct boot_times **bt) {
2265fbf7
SP
264 static struct boot_times times;
265 static bool cached = false;
c170f3a4 266
2265fbf7 267 if (cached)
c170f3a4
LP
268 goto finish;
269
270 assert_cc(sizeof(usec_t) == sizeof(uint64_t));
2265fbf7
SP
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)
c170f3a4 302 return -EIO;
2265fbf7 303
c170f3a4 304 if (times.finish_time <= 0) {
2265fbf7 305 log_error("Bootup is not yet finished. Please try again later.");
c170f3a4 306 return -EAGAIN;
2265fbf7
SP
307 }
308
2265fbf7
SP
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;
c170f3a4
LP
315
316finish:
317 *bt = &times;
318 return 0;
2265fbf7
SP
319}
320
c170f3a4
LP
321static int pretty_boot_time(DBusConnection *bus, char **_buf) {
322 char ts[FORMAT_TIMESPAN_MAX];
2265fbf7 323 struct boot_times *t;
2265fbf7 324 static char buf[4096];
c170f3a4
LP
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;
2265fbf7 332
c170f3a4
LP
333 ptr = buf;
334 size = sizeof(buf);
2265fbf7
SP
335
336 size = strpcpyf(&ptr, size, "Startup finished in ");
337 if (t->firmware_time)
2fa4092c 338 size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC));
2265fbf7 339 if (t->loader_time)
2fa4092c 340 size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC));
2265fbf7 341 if (t->kernel_time)
2fa4092c 342 size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC));
2265fbf7 343 if (t->initrd_time > 0)
2fa4092c 344 size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC));
2265fbf7 345
2fa4092c 346 size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
2265fbf7 347 if (t->kernel_time > 0)
2fa4092c 348 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC));
2265fbf7 349 else
2fa4092c 350 size = strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC));
2265fbf7 351
c170f3a4
LP
352 ptr = strdup(buf);
353 if (!ptr)
354 return log_oom();
355
356 *_buf = ptr;
357 return 0;
2265fbf7
SP
358}
359
c170f3a4
LP
360static void svg_graph_box(double height, double begin, double end) {
361 long long i;
362
2265fbf7
SP
363 /* outside box, fill */
364 svg("<rect class=\"box\" x=\"0\" y=\"0\" width=\"%.03f\" height=\"%.03f\" />\n",
2f6eb835 365 SCALE_X * (end - begin), SCALE_Y * height);
2265fbf7 366
c170f3a4 367 for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) {
2265fbf7 368 /* lines for each second */
c170f3a4 369 if (i % 5000000 == 0)
2265fbf7
SP
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",
2f6eb835 372 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
c170f3a4 373 else if (i % 1000000 == 0)
2265fbf7
SP
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",
2f6eb835 376 SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i);
2265fbf7
SP
377 else
378 svg(" <line class=\"sec01\" x1=\"%.03f\" y1=\"0\" x2=\"%.03f\" y2=\"%.03f\" />\n",
2f6eb835 379 SCALE_X * i, SCALE_X * i, SCALE_Y * height);
2265fbf7
SP
380 }
381}
382
c170f3a4 383static int analyze_plot(DBusConnection *bus) {
2265fbf7
SP
384 struct unit_times *times;
385 struct boot_times *boot;
386 struct utsname name;
387 int n, m = 1, y=0;
388 double width;
c170f3a4
LP
389 _cleanup_free_ char *pretty_times = NULL, *osname = NULL;
390 struct unit_times *u;
2265fbf7 391
c170f3a4
LP
392 n = acquire_boot_times(bus, &boot);
393 if (n < 0)
394 return n;
2265fbf7 395
c170f3a4
LP
396 n = pretty_boot_time(bus, &pretty_times);
397 if (n < 0)
398 return n;
2265fbf7 399
c170f3a4
LP
400 get_os_name(&osname);
401 assert_se(uname(&name) >= 0);
2265fbf7
SP
402
403 n = acquire_time_data(bus, &times);
c170f3a4 404 if (n <= 0)
2265fbf7
SP
405 return n;
406
407 qsort(times, n, sizeof(struct unit_times), compare_unit_start);
408
2f6eb835 409 width = SCALE_X * (boot->firmware_time + boot->finish_time);
2265fbf7
SP
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
c170f3a4 425 for (u = times; u < times + n; u++) {
2265fbf7 426 double len;
c170f3a4 427
2265fbf7
SP
428 if (u->ixt < boot->userspace_time ||
429 u->ixt > boot->finish_time) {
430 free(u->name);
431 u->name = NULL;
432 continue;
433 }
2f6eb835 434 len = ((boot->firmware_time + u->ixt) * SCALE_X)
2265fbf7
SP
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",
2f6eb835 457 80.0 + width, 150.0 + (m * SCALE_Y));
2265fbf7
SP
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"
c170f3a4
LP
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"
2265fbf7
SP
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>",
c170f3a4 491 isempty(osname) ? "Linux" : osname,
2265fbf7
SP
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>",
2f6eb835 494 120.0 + (m *SCALE_Y));
2265fbf7 495
2f6eb835 496 svg("<g transform=\"translate(%.3f,100)\">\n", 20.0 + (SCALE_X * boot->firmware_time));
2265fbf7
SP
497 svg_graph_box(m, -boot->firmware_time, boot->finish_time);
498
499 if (boot->firmware_time) {
c170f3a4
LP
500 svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y);
501 svg_text(true, -(double) boot->firmware_time, y, "firmware");
2265fbf7
SP
502 y++;
503 }
504 if (boot->loader_time) {
c170f3a4
LP
505 svg_bar("loader", -(double) boot->loader_time, 0, y);
506 svg_text(true, -(double) boot->loader_time, y, "loader");
2265fbf7
SP
507 y++;
508 }
509 if (boot->kernel_time) {
510 svg_bar("kernel", 0, boot->kernel_done_time, y);
c170f3a4 511 svg_text(true, 0, y, "kernel");
2265fbf7
SP
512 y++;
513 }
514 if (boot->initrd_time) {
515 svg_bar("initrd", boot->initrd_time, boot->userspace_time, y);
c170f3a4 516 svg_text(true, boot->initrd_time, y, "initrd");
2265fbf7
SP
517 y++;
518 }
519 svg_bar("userspace", boot->userspace_time, boot->finish_time, y);
c170f3a4 520 svg_text("left", boot->userspace_time, y, "userspace");
2265fbf7
SP
521 y++;
522
c170f3a4
LP
523 for (u = times; u < times + n; u++) {
524 char ts[FORMAT_TIMESPAN_MAX];
fd4a5ec6 525 bool b;
c170f3a4 526
2265fbf7
SP
527 if (!u->name)
528 continue;
c170f3a4 529
2265fbf7
SP
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);
c170f3a4 533
fd4a5ec6
ZJS
534 b = u->ixt * SCALE_X > width * 2 / 3;
535 if (u->time)
536 svg_text(b, u->ixt, y, "%s (%s)",
2fa4092c 537 u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC));
c170f3a4 538 else
fd4a5ec6 539 svg_text(b, u->ixt, y, "%s", u->name);
2265fbf7
SP
540 y++;
541 }
542 svg("</g>\n\n");
543
544 svg("</svg>");
c170f3a4
LP
545
546 free_unit_times(times, (unsigned) n);
547
2265fbf7
SP
548 return 0;
549}
550
c170f3a4 551static int analyze_blame(DBusConnection *bus) {
2265fbf7 552 struct unit_times *times;
c170f3a4
LP
553 unsigned i;
554 int n;
555
556 n = acquire_time_data(bus, &times);
557 if (n <= 0)
2265fbf7
SP
558 return n;
559
560 qsort(times, n, sizeof(struct unit_times), compare_unit_time);
561
c170f3a4
LP
562 for (i = 0; i < (unsigned) n; i++) {
563 char ts[FORMAT_TIMESPAN_MAX];
564
565 if (times[i].time > 0)
2fa4092c 566 printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name);
2265fbf7 567 }
c170f3a4
LP
568
569 free_unit_times(times, (unsigned) n);
2265fbf7
SP
570 return 0;
571}
572
c170f3a4
LP
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);
2265fbf7
SP
582 return 0;
583}
584
e55933db 585static int graph_one_property(const char *name, const char *prop, DBusMessageIter *iter, char* patterns[]) {
1700761b
SP
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;
e55933db
ŁS
628 char **p;
629 bool match_found = true;
1700761b
SP
630
631 assert(dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING);
632 dbus_message_iter_get_basic(&sub, &s);
e55933db
Ł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
1700761b
SP
664 printf("\t\"%s\"->\"%s\" %s;\n", name, s, c);
665 }
666 }
667
668 return 0;
669}
670
e55933db 671static int graph_one(DBusConnection *bus, const struct unit_info *u, char *patterns[]) {
1700761b
SP
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);
e55933db 715 r = graph_one_property(u->id, prop, &sub3, patterns);
1700761b
SP
716 if (r < 0)
717 return r;
718 }
719
720 return 0;
721}
722
e55933db 723static int dot(DBusConnection *bus, char* patterns[]) {
1700761b
SP
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
e55933db 758 r = graph_one(bus, &u, patterns);
1700761b
SP
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
2265fbf7
SP
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"
1700761b
SP
785 " --user Connect to user service manager\n"
786 " --order When generating a dependency graph, show only order\n"
f5b7a3fb
LP
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"
2265fbf7 791 "Commands:\n"
1700761b
SP
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",
2265fbf7
SP
796 program_invocation_short_name);
797}
798
799static int parse_argv(int argc, char *argv[])
800{
801 enum {
802 ARG_VERSION = 0x100,
1700761b
SP
803 ARG_ORDER,
804 ARG_REQUIRE,
2265fbf7 805 ARG_USER,
e55933db
ŁS
806 ARG_SYSTEM,
807 ARG_DOT_FROM_PATTERN,
808 ARG_DOT_TO_PATTERN
2265fbf7
SP
809 };
810
811 static const struct option options[] = {
812 { "help", no_argument, NULL, 'h' },
813 { "version", no_argument, NULL, ARG_VERSION },
1700761b
SP
814 { "order", no_argument, NULL, ARG_ORDER },
815 { "require", no_argument, NULL, ARG_REQUIRE },
2265fbf7
SP
816 { "user", no_argument, NULL, ARG_USER },
817 { "system", no_argument, NULL, ARG_SYSTEM },
e55933db
ŁS
818 { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN},
819 { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN },
2265fbf7
SP
820 { NULL, 0, NULL, 0 }
821 };
822
823 assert(argc >= 0);
824 assert(argv);
825
c170f3a4 826 for (;;) {
2265fbf7 827 switch (getopt_long(argc, argv, "h", options, NULL)) {
c170f3a4
LP
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
e55933db
ŁS
853 case ARG_DOT_FROM_PATTERN:
854 arg_dot_from_patterns = strv_append(arg_dot_from_patterns, optarg);
855 break;
856
857 case ARG_DOT_TO_PATTERN:
858 arg_dot_to_patterns = strv_append(arg_dot_to_patterns, optarg);
859 break;
860
c170f3a4
LP
861 case -1:
862 return 1;
863
864 case '?':
865 return -EINVAL;
866
867 default:
868 assert_not_reached("Unhandled option");
2265fbf7
SP
869 }
870 }
871}
872
873int main(int argc, char *argv[]) {
874 int r;
875 DBusConnection *bus = NULL;
876
877 setlocale(LC_ALL, "");
c170f3a4 878 setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */
2265fbf7
SP
879 log_parse_environment();
880 log_open();
881
882 r = parse_argv(argc, argv);
2265fbf7 883 if (r < 0)
c170f3a4
LP
884 return EXIT_FAILURE;
885 else if (r <= 0)
886 return EXIT_SUCCESS;
2265fbf7
SP
887
888 bus = dbus_bus_get(arg_scope == UNIT_FILE_SYSTEM ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, NULL);
889 if (!bus)
c170f3a4 890 return EXIT_FAILURE;
2265fbf7
SP
891
892 if (!argv[optind] || streq(argv[optind], "time"))
893 r = analyze_time(bus);
894 else if (streq(argv[optind], "blame"))
895 r = analyze_blame(bus);
896 else if (streq(argv[optind], "plot"))
897 r = analyze_plot(bus);
1700761b 898 else if (streq(argv[optind], "dot"))
e55933db 899 r = dot(bus, argv+optind+1);
2265fbf7
SP
900 else
901 log_error("Unknown operation '%s'.", argv[optind]);
902
e55933db
ŁS
903 strv_free(arg_dot_from_patterns);
904 strv_free(arg_dot_to_patterns);
2265fbf7 905 dbus_connection_unref(bus);
c170f3a4
LP
906
907 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
2265fbf7 908}