]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
systemd-analyze: Add table and JSON output implementation to plot
authorJoshua Zivkovic <joshua.zivkovic@codethink.co.uk>
Wed, 2 Nov 2022 08:55:50 +0000 (08:55 +0000)
committerjoshuazivkovic <joshua.zivkovic@codethink.co.uk>
Wed, 18 Jan 2023 14:33:08 +0000 (14:33 +0000)
src/analyze/analyze-plot.c
src/analyze/analyze.c
src/analyze/analyze.h

index 100bdc3787c243ea079376ba8d8e3e5a5f3f4426..24f4add099f49e916d408fc35479a887dc11bb7b 100644 (file)
@@ -5,6 +5,7 @@
 #include "analyze-time-data.h"
 #include "bus-error.h"
 #include "bus-map-properties.h"
+#include "format-table.h"
 #include "sort-util.h"
 #include "version.h"
 
@@ -37,7 +38,7 @@ typedef struct HostInfo {
         char *architecture;
 } HostInfo;
 
-static HostInfofree_host_info(HostInfo *hi) {
+static HostInfo *free_host_info(HostInfo *hi) {
         if (!hi)
                 return NULL;
 
@@ -87,7 +88,7 @@ static int acquire_host_info(sd_bus *bus, HostInfo **hi) {
         }
 
         r = bus_map_all_properties(
-                        system_bus ?: bus,
+                        system_bus ? : bus,
                         "org.freedesktop.hostname1",
                         "/org/freedesktop/hostname1",
                         hostname_map,
@@ -156,15 +157,14 @@ static void svg_graph_box(double height, double begin, double end) {
                             SCALE_Y * height);
         }
 }
-
 static int plot_unit_times(UnitTimes *u, double width, int y) {
         bool b;
 
         if (!u->name)
                 return 0;
 
-        svg_bar("activating",   u->activating, u->activated, y);
-        svg_bar("active",       u->activated, u->deactivating, y);
+        svg_bar("activating", u->activating, u->activated, y);
+        svg_bar("active", u->activated, u->deactivating, y);
         svg_bar("deactivating", u->deactivating, u->deactivated, y);
 
         /* place the text on the left if we have passed the half of the svg width */
@@ -178,41 +178,27 @@ static int plot_unit_times(UnitTimes *u, double width, int y) {
         return 1;
 }
 
-int verb_plot(int argc, char *argv[], void *userdata) {
-        _cleanup_(free_host_infop) HostInfo *host = NULL;
-        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
-        _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
-        _cleanup_free_ char *pretty_times = NULL;
-        bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
-        BootTimes *boot;
+static void limit_times_to_boot(const BootTimes *boot, UnitTimes *u) {
+        if (u->deactivated > u->activating && u->deactivated <= boot->finish_time && u->activated == 0
+            && u->deactivating == 0)
+                u->activated = u->deactivating = u->deactivated;
+        if (u->activated < u->activating || u->activated > boot->finish_time)
+                u->activated = boot->finish_time;
+        if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
+                u->deactivating = boot->finish_time;
+        if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
+                u->deactivated = boot->finish_time;
+}
+
+static int produce_plot_as_svg(
+                UnitTimes *times,
+                const HostInfo *host,
+                const BootTimes *boot,
+                const char *pretty_times) {
+        int m = 1, y = 0;
         UnitTimes *u;
-        int n, m = 1, y = 0, r;
         double width;
 
-        r = acquire_bus(&bus, &use_full_bus);
-        if (r < 0)
-                return bus_log_connect_error(r, arg_transport);
-
-        n = acquire_boot_times(bus, &boot);
-        if (n < 0)
-                return n;
-
-        n = pretty_boot_time(bus, &pretty_times);
-        if (n < 0)
-                return n;
-
-        if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
-                n = acquire_host_info(bus, &host);
-                if (n < 0)
-                        return n;
-        }
-
-        n = acquire_time_data(bus, &times);
-        if (n <= 0)
-                return n;
-
-        typesafe_qsort(times, n, compare_unit_start);
-
         width = SCALE_X * (boot->firmware_time + boot->finish_time);
         if (width < 800.0)
                 width = 800.0;
@@ -245,16 +231,8 @@ int verb_plot(int argc, char *argv[], void *userdata) {
                 if (text_width > text_start && text_width + text_start > width)
                         width = text_width + text_start;
 
-                if (u->deactivated > u->activating &&
-                    u->deactivated <= boot->finish_time &&
-                    u->activated == 0 && u->deactivating == 0)
-                        u->activated = u->deactivating = u->deactivated;
-                if (u->activated < u->activating || u->activated > boot->finish_time)
-                        u->activated = boot->finish_time;
-                if (u->deactivating < u->activated || u->deactivating > boot->finish_time)
-                        u->deactivating = boot->finish_time;
-                if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time)
-                        u->deactivated = boot->finish_time;
+                limit_times_to_boot(boot, u);
+
                 m++;
         }
 
@@ -391,5 +369,101 @@ int verb_plot(int argc, char *argv[], void *userdata) {
 
         svg("</svg>\n");
 
+        return 0;
+}
+
+static int show_table(Table *table, const char *word) {
+        int r;
+
+        assert(table);
+        assert(word);
+
+        if (table_get_rows(table) > 1) {
+                table_set_header(table, arg_legend);
+
+                if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+                        r = table_print_json(table, NULL, arg_json_format_flags | JSON_FORMAT_COLOR_AUTO);
+                else
+                        r = table_print(table, NULL);
+                if (r < 0)
+                        return table_log_print_error(r);
+        }
+
+        if (arg_legend) {
+                if (table_get_rows(table) > 1)
+                        printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word);
+                else
+                        printf("No %s.\n", word);
+        }
+
+        return 0;
+}
+
+static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) {
+        _cleanup_(table_unrefp) Table *table = NULL;
+        int r;
+
+        table = table_new("name", "activated", "activating", "time", "deactivated", "deactivating");
+        if (!table)
+                return log_oom();
+
+        for (; times->has_data; times++) {
+                limit_times_to_boot(boot, times);
+
+                r = table_add_many(
+                                table,
+                                TABLE_STRING, times->name,
+                                TABLE_TIMESPAN_MSEC, times->activated,
+                                TABLE_TIMESPAN_MSEC, times->activating,
+                                TABLE_TIMESPAN_MSEC, times->time,
+                                TABLE_TIMESPAN_MSEC, times->deactivated,
+                                TABLE_TIMESPAN_MSEC, times->deactivating);
+                if (r < 0)
+                        return table_log_add_error(r);
+        }
+
+        return show_table(table, "Units");
+}
+
+int verb_plot(int argc, char *argv[], void *userdata) {
+        _cleanup_(free_host_infop) HostInfo *host = NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL;
+        _cleanup_free_ char *pretty_times = NULL;
+        bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM;
+        BootTimes *boot;
+        int n, r;
+
+        r = acquire_bus(&bus, &use_full_bus);
+        if (r < 0)
+                return bus_log_connect_error(r, arg_transport);
+
+        n = acquire_boot_times(bus, &boot);
+        if (n < 0)
+                return n;
+
+        n = pretty_boot_time(bus, &pretty_times);
+        if (n < 0)
+                return n;
+
+        if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) {
+                n = acquire_host_info(bus, &host);
+                if (n < 0)
+                        return n;
+        }
+
+        n = acquire_time_data(bus, &times);
+        if (n <= 0)
+                return n;
+
+        typesafe_qsort(times, n, compare_unit_start);
+
+        if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) || arg_table)
+                r = produce_plot_as_text(times, boot);
+        else
+                r = produce_plot_as_svg(times, host, boot, pretty_times);
+        if (r < 0)
+                return r;
+
         return EXIT_SUCCESS;
 }
index cac2d3adba70847bae44e08f1d41a273862da17b..ef2e60962b3e346ad46a6014c6da832cededdfcf 100644 (file)
@@ -105,6 +105,8 @@ char *arg_unit = NULL;
 JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
 bool arg_quiet = false;
 char *arg_profile = NULL;
+bool arg_legend = true;
+bool arg_table = false;
 
 STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep);
 STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep);
@@ -217,8 +219,10 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --security-policy=PATH  Use custom JSON security policy instead\n"
                "                             of built-in one\n"
                "     --json=pretty|short|off Generate JSON output of the security\n"
-               "                             analysis table\n"
+               "                             analysis table, or plot's raw time data\n"
                "     --no-pager              Do not pipe output into a pager\n"
+               "     --no-legend             Disable column headers and hints in plot\n"
+               "                             with either --table or --json=\n"
                "     --system                Operate on system systemd instance\n"
                "     --user                  Operate on user systemd instance\n"
                "     --global                Operate on global user configuration\n"
@@ -238,6 +242,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "                             specified time\n"
                "     --profile=name|PATH     Include the specified profile in the\n"
                "                             security review of the unit(s)\n"
+               "     --table                 Output plot's raw time data as a table\n"
                "  -h --help                  Show this help\n"
                "     --version               Show package version\n"
                "  -q --quiet                 Do not emit hints\n"
@@ -280,6 +285,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SECURITY_POLICY,
                 ARG_JSON,
                 ARG_PROFILE,
+                ARG_TABLE,
+                ARG_NO_LEGEND,
         };
 
         static const struct option options[] = {
@@ -310,6 +317,8 @@ static int parse_argv(int argc, char *argv[]) {
                 { "unit",             required_argument, NULL, 'U'                  },
                 { "json",             required_argument, NULL, ARG_JSON             },
                 { "profile",          required_argument, NULL, ARG_PROFILE          },
+                { "table",            optional_argument, NULL, ARG_TABLE            },
+                { "no-legend",        optional_argument, NULL, ARG_NO_LEGEND        },
                 {}
         };
 
@@ -448,14 +457,12 @@ static int parse_argv(int argc, char *argv[]) {
                         r = safe_atou(optarg, &arg_iterations);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse iterations: %s", optarg);
-
                         break;
 
                 case ARG_BASE_TIME:
                         r = parse_timestamp(optarg, &arg_base_time);
                         if (r < 0)
                                 return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg);
-
                         break;
 
                 case ARG_PROFILE:
@@ -486,6 +493,15 @@ static int parse_argv(int argc, char *argv[]) {
                         free_and_replace(arg_unit, mangled);
                         break;
                 }
+
+                case ARG_TABLE:
+                        arg_table = true;
+                        break;
+
+                case ARG_NO_LEGEND:
+                        arg_legend = false;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -497,9 +513,9 @@ static int parse_argv(int argc, char *argv[]) {
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                        "Option --offline= is only supported for security right now.");
 
-        if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf"))
+        if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Option --json= is only supported for security and inspect-elf right now.");
+                                       "Option --json= is only supported for security, inspect-elf, and plot right now.");
 
         if (arg_threshold != 100 && !streq_ptr(argv[optind], "security"))
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -536,6 +552,16 @@ static int parse_argv(int argc, char *argv[]) {
         if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used.");
 
+        if ((!arg_legend && !streq_ptr(argv[optind], "plot")) ||
+           (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=.");
+
+        if (arg_table && !streq_ptr(argv[optind], "plot"))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now.");
+
+        if (arg_table && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive.");
+
         return 1; /* work to do */
 }
 
index da12058c43fc3c44e40a2b03450225cb69ed9dd6..e4af7b47e00c965c0625d0abe53cdadb95cfffa3 100644 (file)
@@ -36,6 +36,8 @@ extern char *arg_unit;
 extern JsonFormatFlags arg_json_format_flags;
 extern bool arg_quiet;
 extern char *arg_profile;
+extern bool arg_legend;
+extern bool arg_table;
 
 int acquire_bus(sd_bus **bus, bool *use_full_bus);