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