]>
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; | |
ef215fa7 LP |
79 | int r; |
80 | usec_t service_longest = 0; | |
81 | int to_print = 0; | |
82 | UnitTimes *times; | |
83 | BootTimes *boot; | |
84 | ||
85 | if (strv_extend(units, name)) | |
86 | return log_oom(); | |
87 | ||
88 | r = list_dependencies_get_dependencies(bus, name, &deps); | |
89 | if (r < 0) | |
90 | return r; | |
91 | ||
92 | typesafe_qsort(deps, strv_length(deps), list_dependencies_compare); | |
93 | ||
94 | r = acquire_boot_times(bus, &boot); | |
95 | if (r < 0) | |
96 | return r; | |
97 | ||
98 | STRV_FOREACH(c, deps) { | |
99 | times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */ | |
100 | if (times_in_range(times, boot) && times->activated >= service_longest) | |
101 | service_longest = times->activated; | |
102 | } | |
103 | ||
104 | if (service_longest == 0) | |
105 | return r; | |
106 | ||
107 | STRV_FOREACH(c, deps) { | |
108 | times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */ | |
109 | if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz) | |
110 | to_print++; | |
111 | } | |
112 | ||
113 | if (!to_print) | |
114 | return r; | |
115 | ||
116 | STRV_FOREACH(c, deps) { | |
117 | times = hashmap_get(unit_times_hashmap, *c); /* lgtm [cpp/inconsistent-null-check] */ | |
118 | if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz) | |
119 | continue; | |
120 | ||
121 | to_print--; | |
122 | ||
123 | r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); | |
124 | if (r < 0) | |
125 | return r; | |
126 | ||
127 | if (strv_contains(*units, *c)) { | |
128 | r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), | |
129 | true, NULL, boot); | |
130 | if (r < 0) | |
131 | return r; | |
132 | continue; | |
133 | } | |
134 | ||
135 | r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0)); | |
136 | if (r < 0) | |
137 | return r; | |
138 | ||
139 | if (to_print == 0) | |
140 | break; | |
141 | } | |
142 | return 0; | |
143 | } | |
144 | ||
145 | static int list_dependencies(sd_bus *bus, const char *name) { | |
146 | _cleanup_strv_free_ char **units = NULL; | |
147 | UnitTimes *times; | |
148 | int r; | |
149 | const char *id; | |
150 | _cleanup_free_ char *path = NULL; | |
151 | _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; | |
152 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
153 | BootTimes *boot; | |
154 | ||
155 | assert(bus); | |
156 | ||
157 | path = unit_dbus_path_from_name(name); | |
158 | if (!path) | |
159 | return -ENOMEM; | |
160 | ||
161 | r = sd_bus_get_property( | |
162 | bus, | |
163 | "org.freedesktop.systemd1", | |
164 | path, | |
165 | "org.freedesktop.systemd1.Unit", | |
166 | "Id", | |
167 | &error, | |
168 | &reply, | |
169 | "s"); | |
170 | if (r < 0) | |
171 | return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); | |
172 | ||
173 | r = sd_bus_message_read(reply, "s", &id); | |
174 | if (r < 0) | |
175 | return bus_log_parse_error(r); | |
176 | ||
177 | times = hashmap_get(unit_times_hashmap, id); | |
178 | ||
179 | r = acquire_boot_times(bus, &boot); | |
180 | if (r < 0) | |
181 | return r; | |
182 | ||
183 | if (times) { | |
184 | if (times->time) | |
185 | printf("%s%s +%s%s\n", ansi_highlight_red(), id, | |
186 | FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal()); | |
187 | else if (times->activated > boot->userspace_time) | |
188 | printf("%s @%s\n", id, | |
189 | FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC)); | |
190 | else | |
191 | printf("%s\n", id); | |
192 | } | |
193 | ||
194 | return list_dependencies_one(bus, name, 0, &units, 0); | |
195 | } | |
196 | ||
ef38bedb | 197 | int verb_critical_chain(int argc, char *argv[], void *userdata) { |
ef215fa7 LP |
198 | _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; |
199 | _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; | |
200 | Hashmap *h; | |
201 | int n, r; | |
202 | ||
203 | r = acquire_bus(&bus, NULL); | |
204 | if (r < 0) | |
205 | return bus_log_connect_error(r, arg_transport); | |
206 | ||
207 | n = acquire_time_data(bus, ×); | |
208 | if (n <= 0) | |
209 | return n; | |
210 | ||
211 | h = hashmap_new(&string_hash_ops); | |
212 | if (!h) | |
213 | return log_oom(); | |
214 | ||
215 | for (UnitTimes *u = times; u->has_data; u++) { | |
216 | r = hashmap_put(h, u->name, u); | |
217 | if (r < 0) | |
218 | return log_error_errno(r, "Failed to add entry to hashmap: %m"); | |
219 | } | |
220 | unit_times_hashmap = h; | |
221 | ||
222 | pager_open(arg_pager_flags); | |
223 | ||
224 | puts("The time when unit became active or started is printed after the \"@\" character.\n" | |
225 | "The time the unit took to start is printed after the \"+\" character.\n"); | |
226 | ||
de010b0b | 227 | if (argc > 1) |
ef215fa7 LP |
228 | STRV_FOREACH(name, strv_skip(argv, 1)) |
229 | list_dependencies(bus, *name); | |
de010b0b | 230 | else |
ef215fa7 LP |
231 | list_dependencies(bus, SPECIAL_DEFAULT_TARGET); |
232 | ||
233 | h = hashmap_free(h); | |
234 | return 0; | |
235 | } |