diff options
Diffstat (limited to 'src/analyze/analyze-critical-chain.c')
-rw-r--r-- | src/analyze/analyze-critical-chain.c | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/analyze/analyze-critical-chain.c b/src/analyze/analyze-critical-chain.c new file mode 100644 index 0000000..4a7f452 --- /dev/null +++ b/src/analyze/analyze-critical-chain.c @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "analyze-critical-chain.h" +#include "analyze-time-data.h" +#include "analyze.h" +#include "bus-error.h" +#include "copy.h" +#include "path-util.h" +#include "sort-util.h" +#include "special.h" +#include "static-destruct.h" +#include "strv.h" +#include "terminal-util.h" + +static Hashmap *unit_times_hashmap = NULL; +STATIC_DESTRUCTOR_REGISTER(unit_times_hashmap, hashmap_freep); + +static int list_dependencies_print( + const char *name, + unsigned level, + unsigned branches, + bool last, + UnitTimes *times, + BootTimes *boot) { + + for (unsigned i = level; i != 0; i--) + printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE)); + + printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH)); + + if (times) { + if (timestamp_is_set(times->time)) + printf("%s%s @%s +%s%s", ansi_highlight_red(), name, + FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC), + FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal()); + else if (times->activated > boot->userspace_time) + printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s", name); + } else + printf("%s", name); + printf("\n"); + + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + _cleanup_free_ char *path = NULL; + + assert(bus); + assert(name); + assert(deps); + + path = unit_dbus_path_from_name(name); + if (!path) + return -ENOMEM; + + return bus_get_unit_property_strv(bus, path, "After", deps); +} + +static int list_dependencies_compare(char *const *a, char *const *b) { + usec_t usa = 0, usb = 0; + UnitTimes *times; + + times = hashmap_get(unit_times_hashmap, *a); + if (times) + usa = times->activated; + times = hashmap_get(unit_times_hashmap, *b); + if (times) + usb = times->activated; + + return CMP(usb, usa); +} + +static bool times_in_range(const UnitTimes *times, const BootTimes *boot) { + return times && times->activated > 0 && times->activated <= boot->finish_time; +} + +static int list_dependencies_one(sd_bus *bus, const char *name, unsigned level, char ***units, unsigned branches) { + _cleanup_strv_free_ char **deps = NULL; + int r; + usec_t service_longest = 0; + int to_print = 0; + UnitTimes *times; + BootTimes *boot; + + if (strv_extend(units, name)) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + typesafe_qsort(deps, strv_length(deps), list_dependencies_compare); + + r = acquire_boot_times(bus, /* require_finished = */ true, &boot); + if (r < 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times_in_range(times, boot) && times->activated >= service_longest) + service_longest = times->activated; + } + + if (service_longest == 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times_in_range(times, boot) && service_longest - times->activated <= arg_fuzz) + to_print++; + } + + if (!to_print) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (!times_in_range(times, boot) || service_longest - times->activated > arg_fuzz) + continue; + + to_print--; + + r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); + if (r < 0) + return r; + + if (strv_contains(*units, *c)) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), + true, NULL, boot); + if (r < 0) + return r; + continue; + } + + r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (to_print ? 1 : 0)); + if (r < 0) + return r; + + if (to_print == 0) + break; + } + return 0; +} + +static int list_dependencies(sd_bus *bus, const char *name) { + _cleanup_strv_free_ char **units = NULL; + UnitTimes *times; + int r; + const char *id; + _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + BootTimes *boot; + + assert(bus); + + path = unit_dbus_path_from_name(name); + if (!path) + return -ENOMEM; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &reply, + "s"); + if (r < 0) + return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &id); + if (r < 0) + return bus_log_parse_error(r); + + times = hashmap_get(unit_times_hashmap, id); + + r = acquire_boot_times(bus, /* require_finished = */ true, &boot); + if (r < 0) + return r; + + if (times) { + if (times->time) + printf("%s%s +%s%s\n", ansi_highlight_red(), id, + FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal()); + else if (times->activated > boot->userspace_time) + printf("%s @%s\n", id, + FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s\n", id); + } + + return list_dependencies_one(bus, name, 0, &units, 0); +} + +int verb_critical_chain(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; + int n, r; + + r = acquire_bus(&bus, NULL); + if (r < 0) + return bus_log_connect_error(r, arg_transport); + + n = acquire_time_data(bus, /* require_finished = */ true, ×); + if (n <= 0) + return n; + + for (UnitTimes *u = times; u->has_data; u++) { + r = hashmap_ensure_put(&unit_times_hashmap, &string_hash_ops, u->name, u); + if (r < 0) + return log_error_errno(r, "Failed to add entry to hashmap: %m"); + } + + pager_open(arg_pager_flags); + + puts("The time when unit became active or started is printed after the \"@\" character.\n" + "The time the unit took to start is printed after the \"+\" character.\n"); + + if (argc > 1) + STRV_FOREACH(name, strv_skip(argv, 1)) + list_dependencies(bus, *name); + else + list_dependencies(bus, SPECIAL_DEFAULT_TARGET); + + return EXIT_SUCCESS; +} |