blob: f782f95d5f229b9359537d6bff1c4542db514c71 [file] [log] [blame]
/* 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;
}