1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "analyze-time-data.h"
6 #include "bus-locator.h"
7 #include "bus-map-properties.h"
8 #include "bus-unit-util.h"
9 #include "memory-util.h"
13 static void subtract_timestamp(usec_t
*a
, usec_t b
) {
22 static int log_not_finished(usec_t finish_time
) {
23 return log_error_errno(SYNTHETIC_ERRNO(EINPROGRESS
),
24 "Bootup is not yet finished (org.freedesktop.systemd1.Manager.FinishTimestampMonotonic=%"PRIu64
").\n"
25 "Please try again later.\n"
26 "Hint: Use 'systemctl%s list-jobs' to see active jobs",
28 arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
? "" : " --user");
31 int acquire_boot_times(sd_bus
*bus
, bool require_finished
, BootTimes
**ret
) {
32 static const struct bus_properties_map property_map
[] = {
33 { "FirmwareTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, firmware_time
) },
34 { "LoaderTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, loader_time
) },
35 { "KernelTimestamp", "t", NULL
, offsetof(BootTimes
, kernel_time
) },
36 { "InitRDTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_time
) },
37 { "UserspaceTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, userspace_time
) },
38 { "FinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, finish_time
) },
39 { "SecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_start_time
) },
40 { "SecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, security_finish_time
) },
41 { "ShutdownStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, shutdown_start_time
) },
42 { "GeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_start_time
) },
43 { "GeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, generators_finish_time
) },
44 { "UnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_start_time
) },
45 { "UnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, unitsload_finish_time
) },
46 { "InitRDSecurityStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_start_time
) },
47 { "InitRDSecurityFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_security_finish_time
) },
48 { "InitRDGeneratorsStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_start_time
) },
49 { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_generators_finish_time
) },
50 { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_start_time
) },
51 { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL
, offsetof(BootTimes
, initrd_unitsload_finish_time
) },
52 { "SoftRebootsCount", "t", NULL
, offsetof(BootTimes
, soft_reboots_count
) },
55 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
56 static BootTimes times
;
57 static bool cached
= false;
61 if (require_finished
&& times
.finish_time
<= 0)
62 return log_not_finished(times
.finish_time
);
69 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
71 r
= bus_map_all_properties(
73 "org.freedesktop.systemd1",
74 "/org/freedesktop/systemd1",
81 return log_error_errno(r
, "Failed to get timestamp properties: %s", bus_error_message(&error
, r
));
83 if (require_finished
&& times
.finish_time
<= 0)
84 return log_not_finished(times
.finish_time
);
86 if (arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
&& times
.soft_reboots_count
> 0) {
87 /* On soft-reboot ignore kernel/firmware/initrd times as they are from the previous boot */
88 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
=
89 times
.initrd_security_start_time
= times
.initrd_security_finish_time
=
90 times
.initrd_generators_start_time
= times
.initrd_generators_finish_time
=
91 times
.initrd_unitsload_start_time
= times
.initrd_unitsload_finish_time
= 0;
92 times
.reverse_offset
= times
.shutdown_start_time
;
94 /* Clamp all timestamps to avoid showing huge graphs */
95 if (timestamp_is_set(times
.finish_time
))
96 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
97 subtract_timestamp(×
.userspace_time
, times
.reverse_offset
);
99 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
100 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
102 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
103 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
104 } else if (arg_runtime_scope
== RUNTIME_SCOPE_SYSTEM
&& timestamp_is_set(times
.security_start_time
)) {
105 /* security_start_time is set when systemd is not running under container environment. */
106 if (times
.initrd_time
> 0)
107 times
.kernel_done_time
= times
.initrd_time
;
109 times
.kernel_done_time
= times
.userspace_time
;
112 * User-instance-specific or container-system-specific timestamps processing
113 * (see comment to reverse_offset in BootTimes).
115 times
.reverse_offset
= times
.userspace_time
;
117 times
.firmware_time
= times
.loader_time
= times
.kernel_time
= times
.initrd_time
=
118 times
.userspace_time
= times
.security_start_time
= times
.security_finish_time
= 0;
120 if (times
.finish_time
> 0)
121 subtract_timestamp(×
.finish_time
, times
.reverse_offset
);
123 subtract_timestamp(×
.generators_start_time
, times
.reverse_offset
);
124 subtract_timestamp(×
.generators_finish_time
, times
.reverse_offset
);
126 subtract_timestamp(×
.unitsload_start_time
, times
.reverse_offset
);
127 subtract_timestamp(×
.unitsload_finish_time
, times
.reverse_offset
);
137 static int bus_get_uint64_property(sd_bus
*bus
, const char *path
, const char *interface
, const char *property
, uint64_t *val
) {
138 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
147 r
= sd_bus_get_property_trivial(
149 "org.freedesktop.systemd1",
156 return log_error_errno(r
, "Failed to parse reply: %s", bus_error_message(&error
, r
));
161 int pretty_boot_time(sd_bus
*bus
, char **ret
) {
162 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
163 _cleanup_free_
char *path
= NULL
, *unit_id
= NULL
, *text
= NULL
;
164 usec_t activated_time
= USEC_INFINITY
;
168 r
= acquire_boot_times(bus
, /* require_finished = */ true, &t
);
172 path
= unit_dbus_path_from_name(SPECIAL_DEFAULT_TARGET
);
176 r
= sd_bus_get_property_string(
178 "org.freedesktop.systemd1",
180 "org.freedesktop.systemd1.Unit",
185 log_warning_errno(r
, "default.target doesn't seem to exist, ignoring: %s", bus_error_message(&error
, r
));
187 r
= bus_get_uint64_property(bus
, path
,
188 "org.freedesktop.systemd1.Unit",
189 "ActiveEnterTimestampMonotonic",
192 log_warning_errno(r
, "Could not get time to reach default.target, ignoring: %m");
194 text
= strdup("Startup finished in ");
198 if (timestamp_is_set(t
->firmware_time
) && !strextend(&text
, FORMAT_TIMESPAN(t
->firmware_time
- t
->loader_time
, USEC_PER_MSEC
), " (firmware) + "))
200 if (timestamp_is_set(t
->loader_time
) && !strextend(&text
, FORMAT_TIMESPAN(t
->loader_time
, USEC_PER_MSEC
), " (loader) + "))
202 if (timestamp_is_set(t
->kernel_done_time
) && !strextend(&text
, FORMAT_TIMESPAN(t
->kernel_done_time
, USEC_PER_MSEC
), " (kernel) + "))
204 if (timestamp_is_set(t
->initrd_time
) && !strextend(&text
, FORMAT_TIMESPAN(t
->userspace_time
- t
->initrd_time
, USEC_PER_MSEC
), " (initrd) + "))
206 if (t
->soft_reboots_count
> 0 && strextendf(&text
, "%s (soft reboot #%" PRIu64
") + ", FORMAT_TIMESPAN(t
->userspace_time
, USEC_PER_MSEC
), t
->soft_reboots_count
) < 0)
209 if (!strextend(&text
, FORMAT_TIMESPAN(t
->finish_time
- t
->userspace_time
, USEC_PER_MSEC
), " (userspace) "))
212 if (timestamp_is_set(t
->kernel_done_time
))
213 if (!strextend(&text
, "= ", FORMAT_TIMESPAN(t
->firmware_time
+ t
->finish_time
, USEC_PER_MSEC
), " "))
216 if (unit_id
&& timestamp_is_set(activated_time
)) {
219 /* On soft-reboot times are clamped to avoid showing huge graphs */
220 if (t
->soft_reboots_count
> 0 && timestamp_is_set(t
->userspace_time
))
221 base
= t
->userspace_time
+ t
->reverse_offset
;
223 base
= timestamp_is_set(t
->userspace_time
) ? t
->userspace_time
: t
->reverse_offset
;
225 if (!strextend(&text
, "\n", unit_id
, " reached after ", FORMAT_TIMESPAN(activated_time
- base
, USEC_PER_MSEC
), " in userspace."))
228 } else if (unit_id
&& activated_time
== 0) {
230 if (!strextend(&text
, "\n", unit_id
, " was never reached."))
233 } else if (unit_id
&& activated_time
== USEC_INFINITY
) {
235 if (!strextend(&text
, "\nCould not get time to reach ", unit_id
, "."))
238 } else if (!unit_id
) {
240 if (!strextend(&text
, "\ncould not find default.target."))
244 *ret
= TAKE_PTR(text
);
248 void unit_times_clear(UnitTimes
*t
) {
252 FOREACH_ELEMENT(d
, t
->deps
)
255 t
->name
= mfree(t
->name
);
258 UnitTimes
* unit_times_free_array(UnitTimes
*t
) {
262 for (UnitTimes
*p
= t
; p
->has_data
; p
++)
268 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UnitTimes
*, unit_times_clear
, NULL
);
270 int acquire_time_data(sd_bus
*bus
, bool require_finished
, UnitTimes
**out
) {
271 static const struct bus_properties_map property_map
[] = {
272 { "InactiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activating
) },
273 { "ActiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, activated
) },
274 { "ActiveExitTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivating
) },
275 { "InactiveEnterTimestampMonotonic", "t", NULL
, offsetof(UnitTimes
, deactivated
) },
276 { "After", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_AFTER
]) },
277 { "Before", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_BEFORE
]) },
278 { "Requires", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_REQUIRES
]) },
279 { "Requisite", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_REQUISITE
]) },
280 { "Wants", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_WANTS
]) },
281 { "Conflicts", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_CONFLICTS
]) },
282 { "Upholds", "as", NULL
, offsetof(UnitTimes
, deps
[UNIT_UPHOLDS
]) },
285 _cleanup_(sd_bus_message_unrefp
) sd_bus_message
*reply
= NULL
;
286 _cleanup_(sd_bus_error_free
) sd_bus_error error
= SD_BUS_ERROR_NULL
;
287 _cleanup_(unit_times_free_arrayp
) UnitTimes
*unit_times
= NULL
;
288 BootTimes
*boot_times
;
293 r
= acquire_boot_times(bus
, require_finished
, &boot_times
);
297 r
= bus_call_method(bus
, bus_systemd_mgr
, "ListUnits", &error
, &reply
, NULL
);
299 return log_error_errno(r
, "Failed to list units: %s", bus_error_message(&error
, r
));
301 r
= sd_bus_message_enter_container(reply
, SD_BUS_TYPE_ARRAY
, "(ssssssouso)");
303 return bus_log_parse_error(r
);
305 while ((r
= bus_parse_unit_info(reply
, &u
)) > 0) {
306 _cleanup_(unit_times_clearp
) UnitTimes
*t
= NULL
;
308 if (!GREEDY_REALLOC0(unit_times
, c
+ 2))
311 /* t initially has pointers zeroed by the allocation, and unit_times_clearp will have zeroed
312 * them if the entry is being reused. */
315 assert_cc(sizeof(usec_t
) == sizeof(uint64_t));
317 r
= bus_map_all_properties(
319 "org.freedesktop.systemd1",
327 return log_error_errno(r
, "Failed to get timestamp properties of unit %s: %s",
328 u
.id
, bus_error_message(&error
, r
));
330 /* Activated in the previous soft-reboot iteration? Ignore it, we want new activations */
331 if ((t
->activated
> 0 && t
->activated
< boot_times
->shutdown_start_time
) ||
332 (t
->activating
> 0 && t
->activating
< boot_times
->shutdown_start_time
))
335 subtract_timestamp(&t
->activating
, boot_times
->reverse_offset
);
336 subtract_timestamp(&t
->activated
, boot_times
->reverse_offset
);
338 /* If the last deactivation was in the previous soft-reboot, ignore it */
339 if (boot_times
->soft_reboots_count
> 0) {
340 if (t
->deactivating
< boot_times
->reverse_offset
)
343 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
344 if (t
->deactivated
< boot_times
->reverse_offset
)
347 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
349 subtract_timestamp(&t
->deactivating
, boot_times
->reverse_offset
);
350 subtract_timestamp(&t
->deactivated
, boot_times
->reverse_offset
);
353 if (t
->activated
>= t
->activating
)
354 t
->time
= t
->activated
- t
->activating
;
355 else if (t
->deactivated
>= t
->activating
)
356 t
->time
= t
->deactivated
- t
->activating
;
360 if (t
->activating
== 0)
363 t
->name
= strdup(u
.id
);
368 /* Prevent destructor from running on t reference. */
373 return bus_log_parse_error(r
);
375 *out
= TAKE_PTR(unit_times
);