summaryrefslogtreecommitdiffstats
path: root/src/analyze/analyze-critical-chain.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/analyze/analyze-critical-chain.c230
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..f782f95
--- /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 (times->time > 0)
+ 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, &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, &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, &times);
+ 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;
+}