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
list_dependencies_print(const char * name,unsigned level,unsigned branches,bool last,UnitTimes * times,BootTimes * boot)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
list_dependencies_get_dependencies(sd_bus * bus,const char * name,char *** deps)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
list_dependencies_compare(char * const * a,char * const * b)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
times_in_range(const UnitTimes * times,const BootTimes * boot)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
list_dependencies_one(sd_bus * bus,const char * name,unsigned level,char *** units,unsigned branches)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 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
list_dependencies(sd_bus * bus,const char * name)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
verb_critical_chain(int argc,char * argv[],void * userdata)197 int verb_critical_chain(int argc, char *argv[], void *userdata) {
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
227 if (argc > 1)
228 STRV_FOREACH(name, strv_skip(argv, 1))
229 list_dependencies(bus, *name);
230 else
231 list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
232
233 h = hashmap_free(h);
234 return 0;
235 }
236