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