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