all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version
@ 2026-08-05  8:19 Maximiliano Sandoval
  2026-08-05  8:19 ` [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots Maximiliano Sandoval
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Maximiliano Sandoval @ 2026-08-05  8:19 UTC (permalink / raw)
  To: pve-devel

See commits.


proxmox-mini-journalreader:

Maximiliano Sandoval (1):
  add -B parameter to lists boots

 debian/control           |   2 +-
 src/Makefile             |   2 +-
 src/mini-journalreader.c | 158 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 159 insertions(+), 3 deletions(-)


pve-manager:

Maximiliano Sandoval (1):
  report: list recent reboot events and their kernel version

 PVE/Report.pm | 1 +
 1 file changed, 1 insertion(+)


Summary over all repositories:
  4 files changed, 160 insertions(+), 3 deletions(-)

-- 
Generated by murpp 0.12.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots
  2026-08-05  8:19 [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
@ 2026-08-05  8:19 ` Maximiliano Sandoval
  2026-08-05  9:09   ` Maximiliano Sandoval
  2026-08-05  8:19 ` [PATCH manager 2/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
  2026-08-05  9:50 ` superseded: [PATCH manager/proxmox-mini-journalreader 0/2] " Maximiliano Sandoval
  2 siblings, 1 reply; 5+ messages in thread
From: Maximiliano Sandoval @ 2026-08-05  8:19 UTC (permalink / raw)
  To: pve-devel

The parameter will print all boots, in practice it should be piped into tail.

Sample output:

2026-07-27 08:28	2026-07-27 15:56	3e3813556d53477f9a139a7cb2f3e8f2	7.0.14-6-pve
2026-07-27 15:57	2026-07-27 17:03	5a0324e1ac3f4f24907a21ab81b67384	7.0.14-7-pve
2026-07-28 08:26	2026-07-28 17:07	e62bc71e85274f21a1bfb954be38c071	7.0.14-7-pve
2026-07-29 08:43	2026-07-29 08:48	7f668e4499644ea1b8339d6b8c29d53d	7.0.14-7-pve
2026-07-29 08:49	2026-07-29 17:09	9ff5eb6f0485466383e0b89512f937c9	7.0.14-8-pve

glib is added as a dependency for simplicity of memory management.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---

Notes:
    We usually require the output from `last` or similar in support to query the
    previosly booted kernel versions. We add this helper to avoid adding a
    dependency on either last or wtmpdb on the ISO images.
    
    One awkward thing is that doing sd_journal_seek_tail while inside a
    sd_journal_query_unique results in a segmentation fault, hence we iterate twice.
    
    This seems quick enough for ~400 boots:
    
    time sudo ./proxmox-mini-journalreader -B | wc -l
    417
    
    real	0m0,143s
    user	0m0,004s
    sys	0m0,004s
    
    Open questions:
    
    Should it list the latest boots first? It amounts to reversing the sign on
    boot_info_cmp.

 debian/control           |   2 +-
 src/Makefile             |   2 +-
 src/mini-journalreader.c | 158 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 159 insertions(+), 3 deletions(-)

diff --git a/debian/control b/debian/control
index 8280f6d..d7d8cc0 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,7 @@ Source: proxmox-mini-journalreader
 Section: admin
 Priority: optional
 Maintainer: Proxmox Support Team <support@proxmox.com>
-Build-Depends: debhelper-compat (= 13), libsystemd-dev, pkg-config, scdoc,
+Build-Depends: debhelper-compat (= 13), libsystemd-dev, pkg-config, scdoc, libglib2.0-dev,
 Standards-Version: 4.6.2
 
 Package: proxmox-mini-journalreader
diff --git a/src/Makefile b/src/Makefile
index e64e066..c89e74a 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -5,7 +5,7 @@ LIBEXEC_DIR ?= $(DESTDIR)/usr/libexec/
 MAN1_DIR ?= $(DESTDIR)/usr/share/man/man1
 MANPAGE ?= $(PROGRAM).1
 
-LIBS := libsystemd
+LIBS := libsystemd glib-2.0
 CFLAGS += -Wall -Wextra -Wl,-z,relro -g -O2 --std=gnu11
 CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2
 CFLAGS += $(shell pkg-config --cflags $(LIBS))
diff --git a/src/mini-journalreader.c b/src/mini-journalreader.c
index fc78f65..93e23a0 100644
--- a/src/mini-journalreader.c
+++ b/src/mini-journalreader.c
@@ -21,6 +21,7 @@
 
 #include <errno.h>
 #include <fnmatch.h>
+#include <glib.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -37,6 +38,152 @@ bool json = false;
 bool structured = false;
 bool first_line = true;
 
+typedef struct {
+    char *boot_id;
+    char *version;
+    uint64_t first_timestamp;
+    uint64_t last_timestamp;
+} BootInfo;
+
+static void boot_info_free(BootInfo *info) {
+    g_free(info->boot_id);
+    g_free(info->version);
+    g_free(info);
+}
+
+static int boot_info_cmp(BootInfo *entry1, BootInfo *entry2) {
+    if (entry1->first_timestamp < entry2->first_timestamp) return -1;
+    if (entry1->first_timestamp > entry2->first_timestamp) return 1;
+    return 0;
+}
+
+char* get_version_from_journal_entry(const char *message) {
+    g_autoptr(GRegex) regex = NULL;
+    g_autoptr(GMatchInfo) match_info = NULL;
+    g_autoptr(GError) error = NULL;
+    int start_pos = 0, end_pos = 0;
+
+    g_return_val_if_fail(message != NULL, g_strdup("unknown"));
+
+    regex = g_regex_new("Linux version ([a-zA-Z0-9.-]+)", G_REGEX_OPTIMIZE, G_REGEX_MATCH_DEFAULT, &error);
+    if (error) {
+        g_critical("Regex compilation failed: %s", error->message);
+        return g_strdup("unknown");
+    }
+
+    g_return_val_if_fail(regex != NULL, g_strdup("unknown"));
+
+    g_regex_match(regex, message, G_REGEX_MATCH_DEFAULT, &match_info);
+
+    if (!g_match_info_matches(match_info))
+        return g_strdup("unknown");
+
+    g_match_info_fetch_pos(match_info, 1, &start_pos, &end_pos);
+
+    return g_strndup(message + start_pos, end_pos - start_pos);
+}
+
+char* format_timestamp(uint64_t timestamp) {
+    g_autoptr(GDateTime) time = NULL;
+
+    if (timestamp == 0) {
+        return g_strdup ("-");
+    }
+
+    time = g_date_time_new_from_unix_local_usec(timestamp);
+    return g_date_time_format(time, "%Y-%m-%d %H:%M");
+}
+
+static void print_boot(BootInfo *info, gpointer user_data) {
+    g_autofree char *start = format_timestamp(info->first_timestamp);
+    g_autofree char *end = format_timestamp(info->last_timestamp);
+    g_print("%s\t%s\t%s\t%s\n", start, end, info->boot_id, info->version);
+}
+
+static int list_boots_and_kernels(sd_journal *j, const char* directory) {
+    const void *data;
+    const size_t prefix_len = sizeof("_BOOT_ID");
+    size_t len;
+    int r;
+    sd_journal *jb = NULL;
+    g_autoptr(GPtrArray) boot_ids = NULL;
+    g_autoptr(GPtrArray) boot_array = NULL;
+
+    boot_ids = g_ptr_array_new_with_free_func(g_free);
+
+    r = sd_journal_query_unique(j, "_BOOT_ID");
+    if (r < 0) {
+        g_printerr("Failed to query _BOOT_ID identifier: %s\n", strerror(-r));
+        return 1;
+    }
+    SD_JOURNAL_FOREACH_UNIQUE(j, data, len) {
+        g_ptr_array_add(boot_ids, g_strndup((const char *)data, len));
+    }
+
+    boot_array = g_ptr_array_new_full(boot_ids->len, (GDestroyNotify)boot_info_free);
+
+    if (directory == NULL) {
+        r = sd_journal_open(&jb, SD_JOURNAL_LOCAL_ONLY);
+    } else {
+        r = sd_journal_open_directory(&jb, directory, 0);
+    }
+    if (r < 0) {
+        g_printerr("Failed to open journal: %s\n", strerror(-r));
+        return 1;
+    }
+
+    for (uint i = 0; i < boot_ids->len; i++) {
+        uint64_t first_ts = 0, last_ts = 0;
+        size_t msg_len;
+        const char *message = NULL;
+        const char *match = g_ptr_array_index(boot_ids, i);
+        g_autofree char* version = NULL;
+        g_autofree char *boot_id = NULL;
+
+        boot_id = g_strndup(match + prefix_len, 33); // strips BOOT_ID= from the id
+
+        sd_journal_add_match(jb, match, strlen(match));
+
+        r = sd_journal_seek_head(jb);
+        if (r >= 0) {
+            r = sd_journal_next(jb);
+            if (r > 0) {
+                sd_journal_get_realtime_usec(jb, &first_ts);
+            }
+        }
+
+        r = sd_journal_get_data(jb, "MESSAGE", (const void **)&message, &msg_len);
+        if (r >= 0)
+            version = get_version_from_journal_entry(message);
+        else
+            version = g_strdup("unknown");
+
+        r = sd_journal_seek_tail(jb);
+        if (r >= 0) {
+            r = sd_journal_previous(jb);
+            if (r > 0) {
+                sd_journal_get_realtime_usec(jb, &last_ts);
+            }
+        }
+
+        sd_journal_flush_matches(jb);
+
+        BootInfo *boot_info = g_new0 (BootInfo, 1);
+        boot_info->boot_id = g_steal_pointer(&boot_id);
+        boot_info->version = g_steal_pointer(&version);
+        boot_info->first_timestamp = first_ts;
+        boot_info->last_timestamp = last_ts;
+
+        g_ptr_array_add(boot_array, boot_info);
+    }
+    sd_journal_close(jb);
+
+    g_ptr_array_sort_values(boot_array, (GCompareFunc)boot_info_cmp);
+    g_ptr_array_foreach(boot_array, (GFunc)print_boot, NULL);
+
+    return 0;
+}
+
 // helper to print errors on stderr
 // if we're in json mode, print closing json body if possible
 static void print_error_and_exit(const char *fmt, ...) {
@@ -471,6 +618,7 @@ _Noreturn static void usage(char *error) {
         "  -J\t\t\tprint as json with one object of separate fields per entry\n"
         "  -I\t\t\twith -J, also emit a record listing the distinct syslog identifiers\n"
         "  -U\t\t\twith -J, also emit a record listing the distinct systemd units\n"
+        "  -B\t\t\tList boots. The columns are: the date of the first entry in the boot and last entry, the boot id, and the kernel version\n"
         "  -h\t\t\tthis help\n"
         "\n"
         "Passing no range option will dump all the available journal\n"
@@ -711,11 +859,12 @@ int main(int argc, char *argv[]) {
     bool kernel = false;
     bool list_identifiers = false;
     bool list_units = false;
+    bool list_boots = false;
     int c;
 
     progname = argv[0];
 
-    while ((c = getopt(argc, argv, "b:e:d:n:f:t:p:i:u:jJIUkh")) != -1) {
+    while ((c = getopt(argc, argv, "b:e:d:n:f:t:p:i:u:jJIUkh:B")) != -1) {
         switch (c) {
         case 'b':
             begin = arg_to_timestamp_usec(optarg);
@@ -762,6 +911,9 @@ int main(int argc, char *argv[]) {
         case 'U':
             list_units = true;
             break;
+        case 'B':
+            list_boots = true;
+            break;
         case 'h':
             usage(NULL);
         case '?':
@@ -816,6 +968,10 @@ int main(int argc, char *argv[]) {
         return 1;
     }
 
+    if (list_boots) {
+        return list_boots_and_kernels(j, directory);
+    }
+
     // restrict the traversal before seeking. Each filter is a separate group, conjoined (AND) with
     // the others; within a group matches are OR'd (same field, or an explicit disjunction for the
     // unit), so the groups must be split by a conjunction
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH manager 2/2] report: list recent reboot events and their kernel version
  2026-08-05  8:19 [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
  2026-08-05  8:19 ` [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots Maximiliano Sandoval
@ 2026-08-05  8:19 ` Maximiliano Sandoval
  2026-08-05  9:50 ` superseded: [PATCH manager/proxmox-mini-journalreader 0/2] " Maximiliano Sandoval
  2 siblings, 0 replies; 5+ messages in thread
From: Maximiliano Sandoval @ 2026-08-05  8:19 UTC (permalink / raw)
  To: pve-devel

Based on the most recent reboot events, it is possible to determine whether and
when a crash, fencing, or reboot occurred and with which kernel version the node
booted.

Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---
 PVE/Report.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/PVE/Report.pm b/PVE/Report.pm
index e255d538f..b0164be94 100644
--- a/PVE/Report.pm
+++ b/PVE/Report.pm
@@ -37,6 +37,7 @@ my $init_report_cmds = sub {
                 'hostname',
                 'date -R',
                 'cat /proc/cmdline',
+                '/usr/libexec/proxmox-mini-journalreader -B | tail -n 10',
                 'pveversion --verbose',
                 'cat /etc/hosts',
                 'pvesubscription get',
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots
  2026-08-05  8:19 ` [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots Maximiliano Sandoval
@ 2026-08-05  9:09   ` Maximiliano Sandoval
  0 siblings, 0 replies; 5+ messages in thread
From: Maximiliano Sandoval @ 2026-08-05  9:09 UTC (permalink / raw)
  To: pve-devel

Maximiliano Sandoval <m.sandoval@proxmox.com> writes:

> The parameter will print all boots, in practice it should be piped into tail.
>
> Sample output:
>
> 2026-07-27 08:28	2026-07-27 15:56	3e3813556d53477f9a139a7cb2f3e8f2	7.0.14-6-pve
> 2026-07-27 15:57	2026-07-27 17:03	5a0324e1ac3f4f24907a21ab81b67384	7.0.14-7-pve
> 2026-07-28 08:26	2026-07-28 17:07	e62bc71e85274f21a1bfb954be38c071	7.0.14-7-pve
> 2026-07-29 08:43	2026-07-29 08:48	7f668e4499644ea1b8339d6b8c29d53d	7.0.14-7-pve
> 2026-07-29 08:49	2026-07-29 17:09	9ff5eb6f0485466383e0b89512f937c9	7.0.14-8-pve
>
> glib is added as a dependency for simplicity of memory management.
>
> Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
> ---
>
> Notes:
>     We usually require the output from `last` or similar in support to query the
>     previosly booted kernel versions. We add this helper to avoid adding a
>     dependency on either last or wtmpdb on the ISO images.
>     
>     One awkward thing is that doing sd_journal_seek_tail while inside a
>     sd_journal_query_unique results in a segmentation fault, hence we iterate twice.
>     
>     This seems quick enough for ~400 boots:
>     
>     time sudo ./proxmox-mini-journalreader -B | wc -l
>     417
>     
>     real	0m0,143s
>     user	0m0,004s
>     sys	0m0,004s
>     
>     Open questions:
>     
>     Should it list the latest boots first? It amounts to reversing the sign on
>     boot_info_cmp.
>
> [..]

Found a couple of very minor issues, will send a v2 once there is
feedback. v2 does not need to open a second journal, but still needs the
double iteration.

-- 
Maximiliano




^ permalink raw reply	[flat|nested] 5+ messages in thread

* superseded: [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version
  2026-08-05  8:19 [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
  2026-08-05  8:19 ` [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots Maximiliano Sandoval
  2026-08-05  8:19 ` [PATCH manager 2/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
@ 2026-08-05  9:50 ` Maximiliano Sandoval
  2 siblings, 0 replies; 5+ messages in thread
From: Maximiliano Sandoval @ 2026-08-05  9:50 UTC (permalink / raw)
  To: pve-devel

Maximiliano Sandoval <m.sandoval@proxmox.com> writes:

> See commits.
> [..]

Superseded-by: https://lore.proxmox.com/all/20260805094516.220516-1-m.sandoval@proxmox.com/T/#u
-- 
Maximiliano




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-05  9:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05  8:19 [PATCH manager/proxmox-mini-journalreader 0/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
2026-08-05  8:19 ` [PATCH proxmox-mini-journalreader 1/2] add -B parameter to lists boots Maximiliano Sandoval
2026-08-05  9:09   ` Maximiliano Sandoval
2026-08-05  8:19 ` [PATCH manager 2/2] report: list recent reboot events and their kernel version Maximiliano Sandoval
2026-08-05  9:50 ` superseded: [PATCH manager/proxmox-mini-journalreader 0/2] " Maximiliano Sandoval

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal