diff --git a/config.h.meson b/config.h.meson index 34be5f6a..b002f492 100644 --- a/config.h.meson +++ b/config.h.meson @@ -16,6 +16,8 @@ #mesondefine HAVE_EXECINFO_H +#mesondefine HAVE_LIBSYSTEMD + #mesondefine HAVE_PERF_CLOCKID #mesondefine HAVE_POLKIT diff --git a/meson.build b/meson.build index 3d330d58..373d5f8c 100644 --- a/meson.build +++ b/meson.build @@ -61,6 +61,7 @@ cxx = meson.get_compiler('cpp') glib_dep = dependency('glib-2.0', version: glib_req_version, required: need_glib) gtk_dep = dependency('gtk4', version: gtk_req_version, required: need_gtk) +libsystemd_dep = dependency('libsystemd', required: false) config_h = configuration_data() config_h.set_quoted('SYMBOLIC_VERSION', symbolic_version) @@ -104,6 +105,7 @@ config_h.set('LOCALEDIR', 'PACKAGE_LOCALE_DIR') config_h.set10('ENABLE_NLS', true) config_h.set_quoted('GETTEXT_PACKAGE', 'sysprof') config_h.set_quoted('PACKAGE_LOCALE_DIR', join_paths(get_option('prefix'), get_option('datadir'), 'locale')) +config_h.set10('HAVE_LIBSYSTEMD', libsystemd_dep.found()) polkit_agent_dep = dependency('polkit-agent-1', required: false) config_h.set10('HAVE_POLKIT_AGENT', polkit_agent_dep.found()) diff --git a/src/libsysprof-profile/meson.build b/src/libsysprof-profile/meson.build index 5f17ff0c..24488a98 100644 --- a/src/libsysprof-profile/meson.build +++ b/src/libsysprof-profile/meson.build @@ -14,6 +14,7 @@ libsysprof_profile_public_sources = [ 'sysprof-recording.c', 'sysprof-sampler.c', 'sysprof-spawnable.c', + 'sysprof-system-logs.c', 'sysprof-tracer.c', ] @@ -22,6 +23,7 @@ libsysprof_profile_private_sources = [ 'sysprof-controlfd-instrument.c', 'sysprof-maps-parser.c', 'sysprof-perf-event-stream.c', + 'sysprof-journald-source.c', 'sysprof-podman.c', 'sysprof-polkit.c', ] @@ -44,6 +46,7 @@ libsysprof_profile_public_headers = [ 'sysprof-recording.h', 'sysprof-sampler.h', 'sysprof-spawnable.h', + 'sysprof-system-logs.h', 'sysprof-tracer.h', ] @@ -61,6 +64,7 @@ libsysprof_profile_deps = [ dependency('json-glib-1.0'), dependency('libdex-1', version: dex_req_version), dependency('polkit-gobject-1', version: polkit_req_version), + libsystemd_dep, liblinereader_static_dep, libsysprof_capture_dep, diff --git a/src/libsysprof-profile/sysprof-journald-source.c b/src/libsysprof-profile/sysprof-journald-source.c new file mode 100644 index 00000000..fad3199b --- /dev/null +++ b/src/libsysprof-profile/sysprof-journald-source.c @@ -0,0 +1,107 @@ +/* sysprof-journald-source.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include "sysprof-journald-source.h" + +typedef struct _SysprofJournaldSource +{ + GSource parent_instance; + sd_journal *journal; + SysprofJournaldSourceFunc func; + gpointer func_data; + GDestroyNotify func_data_destroy; +} SysprofJournaldSource; + +static void +sysprof_journald_source_finalize (GSource *source) +{ + SysprofJournaldSource *self = (SysprofJournaldSource *)source; + + if (self->func_data_destroy) + self->func_data_destroy (self->func_data); + + g_clear_pointer (&self->journal, sd_journal_close); +} + +static gboolean +sysprof_journald_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + SysprofJournaldSource *self = (SysprofJournaldSource *)source; + + if (sd_journal_process (self->journal) == SD_JOURNAL_APPEND) + { + if (sd_journal_next (self->journal) > 0) + return self->func (self->func_data, self->journal); + } + + return G_SOURCE_CONTINUE; +} + +static GSourceFuncs sysprof_journald_source_funcs = { + .dispatch = sysprof_journald_source_dispatch, + .finalize = sysprof_journald_source_finalize, +}; + +GSource * +sysprof_journald_source_new (SysprofJournaldSourceFunc func, + gpointer func_data, + GDestroyNotify func_data_destroy) +{ + SysprofJournaldSource *self; + sd_journal *journal = NULL; + int fd; + + if (sd_journal_open (&journal, 0) < 0) + return NULL; + + if (sd_journal_seek_tail (journal) < 0) + goto failure; + + if (sd_journal_previous (journal) < 0) + goto failure; + + if (sd_journal_next (journal) < 0) + goto failure; + + if (-1 == (fd = sd_journal_get_fd (journal))) + goto failure; + + self = (SysprofJournaldSource *)g_source_new (&sysprof_journald_source_funcs, + sizeof (SysprofJournaldSource)); + self->journal = journal; + self->func = func; + self->func_data = func_data; + self->func_data_destroy = func_data_destroy; + + g_source_add_unix_fd ((GSource *)self, fd, sd_journal_get_events (journal)); + + return (GSource *)self; + +failure: + g_clear_pointer (&journal, sd_journal_close); + + return NULL; +} diff --git a/src/libsysprof-profile/sysprof-journald-source.h b/src/libsysprof-profile/sysprof-journald-source.h new file mode 100644 index 00000000..5e4552e9 --- /dev/null +++ b/src/libsysprof-profile/sysprof-journald-source.h @@ -0,0 +1,36 @@ +/* sysprof-journald-source.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +#include + +G_BEGIN_DECLS + +typedef gboolean (*SysprofJournaldSourceFunc) (gpointer user_data, + sd_journal *journal); + +GSource *sysprof_journald_source_new (SysprofJournaldSourceFunc func, + gpointer func_data, + GDestroyNotify func_data_destroy); + +G_END_DECLS diff --git a/src/libsysprof-profile/sysprof-profile.h b/src/libsysprof-profile/sysprof-profile.h index 6640985a..6b312319 100644 --- a/src/libsysprof-profile/sysprof-profile.h +++ b/src/libsysprof-profile/sysprof-profile.h @@ -40,6 +40,7 @@ G_BEGIN_DECLS # include "sysprof-recording.h" # include "sysprof-sampler.h" # include "sysprof-spawnable.h" +# include "sysprof-system-logs.h" # include "sysprof-tracer.h" #undef SYSPROF_PROFILE_INSIDE diff --git a/src/libsysprof-profile/sysprof-system-logs.c b/src/libsysprof-profile/sysprof-system-logs.c new file mode 100644 index 00000000..6652aba7 --- /dev/null +++ b/src/libsysprof-profile/sysprof-system-logs.c @@ -0,0 +1,194 @@ +/* sysprof-system-logs.c + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "config.h" + +#include + +#include +#include + +#include "sysprof-system-logs.h" +#include "sysprof-instrument-private.h" +#include "sysprof-recording-private.h" + +#ifdef HAVE_LIBSYSTEMD +# include "sysprof-journald-source.h" +#endif + +struct _SysprofSystemLogs +{ + SysprofInstrument parent_instance; +}; + +struct _SysprofSystemLogsClass +{ + SysprofInstrumentClass parent_class; +}; + +G_DEFINE_FINAL_TYPE (SysprofSystemLogs, sysprof_system_logs, SYSPROF_TYPE_INSTRUMENT) + +#ifdef HAVE_LIBSYSTEMD +static char * +journal_get_data (sd_journal *journal, + const char *field) +{ + gconstpointer data = NULL; + const char *endptr; + const char *eq; + gsize length; + int ret; + + g_assert (journal != NULL); + g_assert (field != NULL); + + if ((ret = sd_journal_get_data (journal, field, &data, &length)) < 0) + return NULL; + + endptr = (const char *)data + length; + if (!(eq = memchr (data, '=', length))) + return NULL; + + eq++; + + return g_strndup (eq, endptr - eq); +} + +static gboolean +sysprof_system_logs_callback (SysprofCaptureWriter *writer, + sd_journal *journal) +{ + g_autofree char *message = NULL; + g_autofree char *priority = NULL; + sd_id128_t boot_id; + guint64 usec; + int severity = 0; + + g_assert (journal != NULL); + g_assert (writer != NULL); + + if (sd_journal_get_monotonic_usec (journal, &usec, &boot_id) != 0 || + !(message = journal_get_data (journal, "MESSAGE"))) + return G_SOURCE_CONTINUE; + + priority = journal_get_data (journal, "PRIORITY"); + + if (priority != NULL) + { + switch (priority[0]) + { + case '0': + case '1': + severity = G_LOG_LEVEL_ERROR; + break; + + case '2': + severity = G_LOG_LEVEL_CRITICAL; + break; + + case '3': + case '4': + severity = G_LOG_LEVEL_WARNING; + break; + + default: + case '5': + severity = G_LOG_LEVEL_MESSAGE; + break; + + case '6': + severity = G_LOG_LEVEL_INFO; + break; + + case '7': + severity = G_LOG_LEVEL_DEBUG; + break; + } + } + + sysprof_capture_writer_add_log (writer, usec*1000, -1, -1, severity, "System Log", message); + + return G_SOURCE_CONTINUE; +} +#endif + +#ifdef HAVE_LIBSYSTEMD +static DexFuture * +sysprof_system_logs_record_finished (DexFuture *future, + gpointer user_data) +{ + GSource *source = user_data; + g_source_destroy (source); + return dex_future_new_for_boolean (TRUE); +} +#endif + +static DexFuture * +sysprof_system_logs_record (SysprofInstrument *instrument, + SysprofRecording *recording, + GCancellable *cancellable) +{ +#ifdef HAVE_LIBSYSTEMD + SysprofCaptureWriter *writer; + GSource *source; + + g_assert (SYSPROF_IS_SYSTEM_LOGS (instrument)); + g_assert (SYSPROF_IS_RECORDING (recording)); + g_assert (G_IS_CANCELLABLE (cancellable)); + + writer = _sysprof_recording_writer (recording); + + source = sysprof_journald_source_new ((SysprofJournaldSourceFunc)sysprof_system_logs_callback, + sysprof_capture_writer_ref (writer), + (GDestroyNotify)sysprof_capture_writer_unref); + g_source_set_static_name (source, "[sysprof-journald]"); + g_source_set_priority (source, G_PRIORITY_LOW); + g_source_attach (source, NULL); + + return dex_future_finally (dex_cancellable_new_from_cancellable (cancellable), + sysprof_system_logs_record_finished, + source, + (GDestroyNotify)g_source_unref); +#else + _sysprof_recording_diagnostic (recording, + _("System Logs"), + _("Recording system logs is not supported on your platform.")); + return dex_future_new_for_boolean (TRUE); +#endif +} + +static void +sysprof_system_logs_class_init (SysprofSystemLogsClass *klass) +{ + SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); + + instrument_class->record = sysprof_system_logs_record; +} + +static void +sysprof_system_logs_init (SysprofSystemLogs *self) +{ +} + +SysprofInstrument * +sysprof_system_logs_new (void) +{ + return g_object_new (SYSPROF_TYPE_SYSTEM_LOGS, NULL); +} diff --git a/src/libsysprof-profile/sysprof-system-logs.h b/src/libsysprof-profile/sysprof-system-logs.h new file mode 100644 index 00000000..1739f722 --- /dev/null +++ b/src/libsysprof-profile/sysprof-system-logs.h @@ -0,0 +1,43 @@ +/* sysprof-system-logs.h + * + * Copyright 2023 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include "sysprof-instrument.h" + +G_BEGIN_DECLS + +#define SYSPROF_TYPE_SYSTEM_LOGS (sysprof_system_logs_get_type()) +#define SYSPROF_IS_SYSTEM_LOGS(obj) G_TYPE_CHECK_INSTANCE_TYPE(obj, SYSPROF_TYPE_SYSTEM_LOGS) +#define SYSPROF_SYSTEM_LOGS(obj) G_TYPE_CHECK_INSTANCE_CAST(obj, SYSPROF_TYPE_SYSTEM_LOGS, SysprofSystemLogs) +#define SYSPROF_SYSTEM_LOGS_CLASS(klass) G_TYPE_CHECK_CLASS_CAST(klass, SYSPROF_TYPE_SYSTEM_LOGS, SysprofSystemLogsClass) + +typedef struct _SysprofSystemLogs SysprofSystemLogs; +typedef struct _SysprofSystemLogsClass SysprofSystemLogsClass; + +SYSPROF_AVAILABLE_IN_ALL +GType sysprof_system_logs_get_type (void) G_GNUC_CONST; +SYSPROF_AVAILABLE_IN_ALL +SysprofInstrument *sysprof_system_logs_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (SysprofSystemLogs, g_object_unref) + +G_END_DECLS + diff --git a/src/libsysprof-profile/tests/test-profiler.c b/src/libsysprof-profile/tests/test-profiler.c index 1aa3264d..af88276a 100644 --- a/src/libsysprof-profile/tests/test-profiler.c +++ b/src/libsysprof-profile/tests/test-profiler.c @@ -178,6 +178,7 @@ main (int argc, sysprof_profiler_add_instrument (profiler, sysprof_energy_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_memory_usage_new ()); sysprof_profiler_add_instrument (profiler, sysprof_network_usage_new ()); + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); if (power_profile) sysprof_profiler_add_instrument (profiler, sysprof_power_profile_new (power_profile)); diff --git a/src/sysprof/sysprof-greeter.c b/src/sysprof/sysprof-greeter.c index c7176adf..81d596ba 100644 --- a/src/sysprof/sysprof-greeter.c +++ b/src/sysprof/sysprof-greeter.c @@ -44,6 +44,7 @@ struct _SysprofGreeter GtkSwitch *record_disk_usage; GtkSwitch *record_network_usage; GtkSwitch *record_compositor; + GtkSwitch *record_system_logs; }; enum { @@ -91,6 +92,9 @@ sysprof_greeter_create_profiler (SysprofGreeter *self) "org.gnome.Shell", "/org/gnome/Sysprof3/Profiler")); + if (gtk_switch_get_active (self->record_system_logs)) + sysprof_profiler_add_instrument (profiler, sysprof_system_logs_new ()); + return g_steal_pointer (&profiler); } @@ -282,6 +286,7 @@ sysprof_greeter_class_init (SysprofGreeterClass *klass) gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_disk_usage); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_network_usage); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_page); + gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, record_system_logs); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, sample_native_stacks); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, toolbar); gtk_widget_class_bind_template_child (widget_class, SysprofGreeter, view_stack); diff --git a/src/sysprof/sysprof-greeter.ui b/src/sysprof/sysprof-greeter.ui index 2e9bbc94..796c9e5d 100644 --- a/src/sysprof/sysprof-greeter.ui +++ b/src/sysprof/sysprof-greeter.ui @@ -223,6 +223,24 @@ + + + System + + + record_system_logs + System Logs + Watch the system log for new messages and record them + + + true + center + + + + + +