]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze-critical-chain.c
Merge pull request #25608 from poettering/dissect-moar
[thirdparty/systemd.git] / src / analyze / analyze-critical-chain.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "analyze-critical-chain.h"
4 #include "analyze-time-data.h"
5 #include "analyze.h"
6 #include "bus-error.h"
7 #include "copy.h"
8 #include "path-util.h"
9 #include "sort-util.h"
10 #include "special.h"
11 #include "static-destruct.h"
12 #include "strv.h"
13 #include "terminal-util.h"
14
15 static Hashmap *unit_times_hashmap = NULL;
16 STATIC_DESTRUCTOR_REGISTER(unit_times_hashmap, hashmap_freep);
17
18 static int list_dependencies_print(
19 const char *name,
20 unsigned level,
21 unsigned branches,
22 bool last,
23 UnitTimes *times,
24 BootTimes *boot) {
25
26 for (unsigned i = level; i != 0; i--)
27 printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE));
28
29 printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH));
30
31 if (times) {
32 if (times->time > 0)
33 printf("%s%s @%s +%s%s", ansi_highlight_red(), name,
34 FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC),
35 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
36 else if (times->activated > boot->userspace_time)
37 printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
38 else
39 printf("%s", name);
40 } else
41 printf("%s", name);
42 printf("\n");
43
44 return 0;
45 }
46
47 static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) {
48 _cleanup_free_ char *path = NULL;
49
50 assert(bus);
51 assert(name);
52 assert(deps);
53
54 path = unit_dbus_path_from_name(name);
55 if (!path)
56 return -ENOMEM;
57
58 return bus_get_unit_property_strv(bus, path, "After", deps);
59 }
60
61 static int list_dependencies_compare(char *const *a, char *const *b) {
62 usec_t usa = 0, usb = 0;
63 UnitTimes *times;
64
65 times = hashmap_get(unit_times_hashmap, *a);
66 if (times)
67 usa = times->activated;
68 times = hashmap_get(unit_times_hashmap, *b);
69 if (times)
70 usb = times->activated;
71
72 return CMP(usb, usa);
73 }
74
75 static bool times_in_range(const UnitTimes *times, const BootTimes *boot) {
76 return times && times->activated > 0 && times->activated <= boot->finish_time;
77 }
78
79 static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) {
80 _cleanup_strv_free_ char **deps = NULL;
81 int r;
82 usec_t service_longest = 0;
83 int to_print = 0;
84 UnitTimes *times;
85 BootTimes *boot;
86
87 if (strv_extend(units, name))
88 return log_oom();
89
90 r = list_dependencies_get_dependencies(bus, name, &deps);
91 if (r < 0)
92 return r;
93
94 typesafe_qsort(deps, strv_length(deps), list_dependencies_compare);
95
96 r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
97 if (r < 0)
98 return r;
99
100 STRV_FOREACH(c, deps) {
101 times = hashmap_get(unit_times_hashmap, *c);
102 if (times_in_range(times, boot) && times->activated >= service_longest)
103 service_longest = times->activated;
104 }
105
106 if (service_longest == 0)
107 return r;
108
109 STRV_FOREACH(c, deps) {
110 times = hashmap_get(unit_times_hashmap, *c);
111 if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz)
112 to_print++;
113 }
114
115 if (!to_print)
116 return r;
117
118 STRV_FOREACH(c, deps) {
119 times = hashmap_get(unit_times_hashmap, *c);
120 if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz)
121 continue;
122
123 to_print--;
124
125 r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot);
126 if (r < 0)
127 return r;
128
129 if (strv_contains(*units, *c)) {
130 r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0),
131 true, NULL, boot);
132 if (r < 0)
133 return r;
134 continue;
135 }
136
137 r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0));
138 if (r < 0)
139 return r;
140
141 if (to_print == 0)
142 break;
143 }
144 return 0;
145 }
146
147 static int list_dependencies(sd_bus *bus, const char *name) {
148 _cleanup_strv_free_ char **units = NULL;
149 UnitTimes *times;
150 int r;
151 const char *id;
152 _cleanup_free_ char *path = NULL;
153 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
154 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
155 BootTimes *boot;
156
157 assert(bus);
158
159 path = unit_dbus_path_from_name(name);
160 if (!path)
161 return -ENOMEM;
162
163 r = sd_bus_get_property(
164 bus,
165 "org.freedesktop.systemd1",
166 path,
167 "org.freedesktop.systemd1.Unit",
168 "Id",
169 &error,
170 &reply,
171 "s");
172 if (r < 0)
173 return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
174
175 r = sd_bus_message_read(reply, "s", &id);
176 if (r < 0)
177 return bus_log_parse_error(r);
178
179 times = hashmap_get(unit_times_hashmap, id);
180
181 r = acquire_boot_times(bus, /* require_finished = */ true, &boot);
182 if (r < 0)
183 return r;
184
185 if (times) {
186 if (times->time)
187 printf("%s%s +%s%s\n", ansi_highlight_red(), id,
188 FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal());
189 else if (times->activated > boot->userspace_time)
190 printf("%s @%s\n", id,
191 FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC));
192 else
193 printf("%s\n", id);
194 }
195
196 return list_dependencies_one(bus, name, 0, &units, 0);
197 }
198
199 int verb_critical_chain(int argc, char *argv[], void *userdata) {
200 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
201 _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
202 int n, r;
203
204 r = acquire_bus(&bus, NULL);
205 if (r < 0)
206 return bus_log_connect_error(r, arg_transport);
207
208 n = acquire_time_data(bus, /* require_finished = */ true, &times);
209 if (n <= 0)
210 return n;
211
212 for (UnitTimes *u = times; u->has_data; u++) {
213 r = hashmap_ensure_put(&unit_times_hashmap, &string_hash_ops, u->name, u);
214 if (r < 0)
215 return log_error_errno(r, "Failed to add entry to hashmap: %m");
216 }
217
218 pager_open(arg_pager_flags);
219
220 puts("The time when unit became active or started is printed after the \"@\" character.\n"
221 "The time the unit took to start is printed after the \"+\" character.\n");
222
223 if (argc > 1)
224 STRV_FOREACH(name, strv_skip(argv, 1))
225 list_dependencies(bus, *name);
226 else
227 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
228
229 return EXIT_SUCCESS;
230 }