analyze: split out critical chain
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "analyze.h"
|
||||
#include "analyze-critical-chain.h"
|
||||
#include "analyze-time-data.h"
|
||||
#include "strv.h"
|
||||
#include "copy.h"
|
||||
#include "path-util.h"
|
||||
#include "terminal-util.h"
|
||||
#include "sort-util.h"
|
||||
#include "special.h"
|
||||
#include "bus-error.h"
|
||||
|
||||
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 Hashmap *unit_times_hashmap;
|
||||
|
||||
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;
|
||||
char **c;
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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 analyze_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;
|
||||
Hashmap *h;
|
||||
int n, r;
|
||||
|
||||
r = acquire_bus(&bus, NULL);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport);
|
||||
|
||||
n = acquire_time_data(bus, ×);
|
||||
if (n <= 0)
|
||||
return n;
|
||||
|
||||
h = hashmap_new(&string_hash_ops);
|
||||
if (!h)
|
||||
return log_oom();
|
||||
|
||||
for (UnitTimes *u = times; u->has_data; u++) {
|
||||
r = hashmap_put(h, u->name, u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add entry to hashmap: %m");
|
||||
}
|
||||
unit_times_hashmap = h;
|
||||
|
||||
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) {
|
||||
char **name;
|
||||
STRV_FOREACH(name, strv_skip(argv, 1))
|
||||
list_dependencies(bus, *name);
|
||||
} else
|
||||
list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
|
||||
|
||||
h = hashmap_free(h);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int analyze_critical_chain(int argc, char *argv[], void *userdata);
|
||||
+2
-226
@@ -18,6 +18,7 @@
|
||||
#include "analyze-capability.h"
|
||||
#include "analyze-cat-config.h"
|
||||
#include "analyze-condition.h"
|
||||
#include "analyze-critical-chain.h"
|
||||
#include "analyze-dot.h"
|
||||
#include "analyze-dump.h"
|
||||
#include "analyze-elf.h"
|
||||
@@ -83,7 +84,7 @@
|
||||
|
||||
DotMode arg_dot = DEP_ALL;
|
||||
char **arg_dot_from_patterns = NULL, **arg_dot_to_patterns = NULL;
|
||||
static usec_t arg_fuzz = 0;
|
||||
usec_t arg_fuzz = 0;
|
||||
PagerFlags arg_pager_flags = 0;
|
||||
BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
|
||||
const char *arg_host = NULL;
|
||||
@@ -196,231 +197,6 @@ static int process_aliases(char *argv[], char *tempdir, char ***ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 Hashmap *unit_times_hashmap;
|
||||
|
||||
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;
|
||||
char **c;
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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); /* lgtm [cpp/inconsistent-null-check] */
|
||||
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);
|
||||
}
|
||||
|
||||
static int analyze_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;
|
||||
Hashmap *h;
|
||||
int n, r;
|
||||
|
||||
r = acquire_bus(&bus, NULL);
|
||||
if (r < 0)
|
||||
return bus_log_connect_error(r, arg_transport);
|
||||
|
||||
n = acquire_time_data(bus, ×);
|
||||
if (n <= 0)
|
||||
return n;
|
||||
|
||||
h = hashmap_new(&string_hash_ops);
|
||||
if (!h)
|
||||
return log_oom();
|
||||
|
||||
for (UnitTimes *u = times; u->has_data; u++) {
|
||||
r = hashmap_put(h, u->name, u);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add entry to hashmap: %m");
|
||||
}
|
||||
unit_times_hashmap = h;
|
||||
|
||||
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) {
|
||||
char **name;
|
||||
STRV_FOREACH(name, strv_skip(argv, 1))
|
||||
list_dependencies(bus, *name);
|
||||
} else
|
||||
list_dependencies(bus, SPECIAL_DEFAULT_TARGET);
|
||||
|
||||
h = hashmap_free(h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int analyze_time(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef enum DotMode {
|
||||
|
||||
extern DotMode arg_dot;
|
||||
extern char **arg_dot_from_patterns, **arg_dot_to_patterns;
|
||||
extern usec_t arg_fuzz;
|
||||
extern PagerFlags arg_pager_flags;
|
||||
extern BusTransport arg_transport;
|
||||
extern const char *arg_host;
|
||||
|
||||
@@ -11,6 +11,8 @@ systemd_analyze_sources = files('''
|
||||
analyze-cat-config.h
|
||||
analyze-condition.c
|
||||
analyze-condition.h
|
||||
analyze-critical-chain.c
|
||||
analyze-critical-chain.h
|
||||
analyze-dot.c
|
||||
analyze-dot.h
|
||||
analyze-dump.c
|
||||
|
||||
Reference in New Issue
Block a user