]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
analyze: add tooltips with dependency information to "plot"
authorJade Lovelace <software@lfcode.ca>
Sun, 1 Oct 2023 05:21:33 +0000 (22:21 -0700)
committerJade Lovelace <software@lfcode.ca>
Wed, 4 Oct 2023 05:50:25 +0000 (22:50 -0700)
This helps a lot with figuring out why units were started when they
were, rather than guessing there is a dependency relation. We could
perhaps also do fun JavaScript things in the future to highlight
dependencies on mouse-over.

src/analyze/analyze-plot.c
src/analyze/analyze-time-data.c
src/analyze/analyze-time-data.h

index 0fbe17465e06a34cf57c5f6c0802619f86a8c217..81fc25b31e0370d492ea81290d1ecb6836faf8b6 100644 (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;
                 }
 
index 70717fc4b27d99f5f6e4c41ec7dc9329259de449..741cab33b67113d25e2b532ddb9a3b359750daf1 100644 (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)
index 79240745cb36c2dbfde091915f48a262b142c0f4..9049d876b258defecc5a69be84f2370280c8f98c 100644 (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);