Merge pull request #29391 from lf-/jade/analyze-plot-tooltips

analyze: add tooltips with dependency information to "plot"
This commit is contained in:
Luca Boccassi 2023-10-04 21:15:52 +01:00 committed by GitHub
commit 723ce80602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 67 additions and 12 deletions

6
NEWS
View file

@ -60,6 +60,12 @@ CHANGES WITH 255 in spe:
is now dropped, as it never worked, hence it should not be used by
anyone.
Changes in systemd-analyze:
* "systemd-analyze plot" has gained tooltips on each unit name with
related-unit information in its svg output, such as Before=,
Requires=, and similar properties.
CHANGES WITH 254:
Announcements of Future Feature Removals and Incompatible Changes:

View file

@ -1,13 +1,15 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-plot.h"
#include "analyze-time-data.h"
#include "analyze.h"
#include "bus-error.h"
#include "bus-map-properties.h"
#include "format-table.h"
#include "os-util.h"
#include "sort-util.h"
#include "strv.h"
#include "unit-def.h"
#include "version.h"
#define SCALE_X (0.1 / 1000.0) /* pixels per us */
@ -158,12 +160,32 @@ static void svg_graph_box(double height, double begin, double end) {
SCALE_Y * height);
}
}
static void plot_tooltip(const UnitTimes *ut) {
assert(ut);
assert(ut->name);
svg("%s:\n", ut->name);
UnitDependency i;
VA_ARGS_FOREACH(i, UNIT_AFTER, UNIT_BEFORE, UNIT_REQUIRES, UNIT_REQUISITE, UNIT_WANTS, UNIT_CONFLICTS, UNIT_UPHOLDS)
if (!strv_isempty(ut->deps[i])) {
svg("\n%s:\n", unit_dependency_to_string(i));
STRV_FOREACH(s, ut->deps[i])
svg(" %s\n", *s);
}
}
static int plot_unit_times(UnitTimes *u, double width, int y) {
bool b;
if (!u->name)
return 0;
svg("<g>\n");
svg("<title>");
plot_tooltip(u);
svg("</title>\n");
svg_bar("activating", u->activating, u->activated, y);
svg_bar("active", u->activated, u->deactivating, y);
svg_bar("deactivating", u->deactivating, u->deactivated, y);
@ -175,6 +197,7 @@ static int plot_unit_times(UnitTimes *u, double width, int y) {
u->name, FORMAT_TIMESPAN(u->time, USEC_PER_MSEC));
else
svg_text(b, u->activating, y, "%s", u->name);
svg("</g>\n");
return 1;
}
@ -220,7 +243,7 @@ static int produce_plot_as_svg(
double text_start, text_width;
if (u->activating > boot->finish_time) {
u->name = mfree(u->name);
unit_times_clear(u);
continue;
}

View file

@ -1,12 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "analyze.h"
#include "analyze-time-data.h"
#include "analyze.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "bus-map-properties.h"
#include "bus-unit-util.h"
#include "memory-util.h"
#include "special.h"
#include "strv.h"
static void subtract_timestamp(usec_t *a, usec_t b) {
assert(a);
@ -215,22 +217,41 @@ int pretty_boot_time(sd_bus *bus, char **ret) {
return 0;
}
void unit_times_clear(UnitTimes *t) {
if (!t)
return;
FOREACH_ARRAY(d, t->deps, ELEMENTSOF(t->deps))
*d = strv_free(*d);
t->name = mfree(t->name);
}
UnitTimes* unit_times_free_array(UnitTimes *t) {
if (!t)
return NULL;
for (UnitTimes *p = t; p->has_data; p++)
free(p->name);
unit_times_clear(p);
return mfree(t);
}
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(UnitTimes*, unit_times_clear, NULL);
int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
static const struct bus_properties_map property_map[] = {
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
{ "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
{ "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
{ "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
{ "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activating) },
{ "ActiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, activated) },
{ "ActiveExitTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivating) },
{ "InactiveEnterTimestampMonotonic", "t", NULL, offsetof(UnitTimes, deactivated) },
{ "After", "as", NULL, offsetof(UnitTimes, deps[UNIT_AFTER]) },
{ "Before", "as", NULL, offsetof(UnitTimes, deps[UNIT_BEFORE]) },
{ "Requires", "as", NULL, offsetof(UnitTimes, deps[UNIT_REQUIRES]) },
{ "Requisite", "as", NULL, offsetof(UnitTimes, deps[UNIT_REQUISITE]) },
{ "Wants", "as", NULL, offsetof(UnitTimes, deps[UNIT_WANTS]) },
{ "Conflicts", "as", NULL, offsetof(UnitTimes, deps[UNIT_CONFLICTS]) },
{ "Upholds", "as", NULL, offsetof(UnitTimes, deps[UNIT_UPHOLDS]) },
{},
};
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
@ -254,14 +275,14 @@ int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
return bus_log_parse_error(r);
while ((r = bus_parse_unit_info(reply, &u)) > 0) {
UnitTimes *t;
_cleanup_(unit_times_clearp) UnitTimes *t = NULL;
if (!GREEDY_REALLOC(unit_times, c + 2))
if (!GREEDY_REALLOC0(unit_times, c + 2))
return log_oom();
unit_times[c + 1].has_data = false;
/* t initially has pointers zeroed by the allocation, and unit_times_clearp will have zeroed
* them if the entry is being reused. */
t = &unit_times[c];
t->name = NULL;
assert_cc(sizeof(usec_t) == sizeof(uint64_t));
@ -298,6 +319,8 @@ int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) {
return log_oom();
t->has_data = true;
/* Prevent destructor from running on t reference. */
TAKE_PTR(t);
c++;
}
if (r < 0)

View file

@ -4,6 +4,7 @@
#include <sd-bus.h>
#include "time-util.h"
#include "unit-def.h"
typedef struct BootTimes {
usec_t firmware_time;
@ -45,11 +46,13 @@ typedef struct UnitTimes {
usec_t deactivated;
usec_t deactivating;
usec_t time;
char **deps[_UNIT_DEPENDENCY_MAX];
} UnitTimes;
int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret);
int pretty_boot_time(sd_bus *bus, char **ret);
void unit_times_clear(UnitTimes *t);
UnitTimes* unit_times_free_array(UnitTimes *t);
DEFINE_TRIVIAL_CLEANUP_FUNC(UnitTimes*, unit_times_free_array);