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, &times);
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