]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/analyze/analyze-critical-chain.c
d3169791d9f2494871c51186ecdc51f0c5ccc793
[thirdparty/systemd.git] / src / analyze / analyze-critical-chain.c
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 verb_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, &times);
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 }