/* sysprof-scheduler-details.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 "sysprof-instrument-private.h" #include "sysprof-perf-event-stream-private.h" #include "sysprof-scheduler-details.h" #include "sysprof-recording-private.h" #include "line-reader-private.h" struct _SysprofSchedulerDetails { SysprofInstrument parent_instance; SysprofRecording *recording; DexFuture *cancellable; GPtrArray *streams; gint64 *last_switch_times; gint64 started_at; gint64 ended_at; gint64 sched_switch_id; gint64 prev_comm_offset; gint64 prev_pid_offset; gint64 prev_state_offset; }; struct _SysprofSchedulerDetailsClass { SysprofInstrumentClass parent_class; }; G_DEFINE_FINAL_TYPE (SysprofSchedulerDetails, sysprof_scheduler_details, SYSPROF_TYPE_INSTRUMENT) static void sysprof_scheduler_details_event_cb (const SysprofPerfEvent *event, guint cpu, gpointer user_data) { SysprofSchedulerDetails *self = user_data; const guint8 *raw; gint64 begin, end; glong prev_state; char prev_comm[16]; char name[8]; GPid prev_pid; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); if (event->header.type != PERF_RECORD_SAMPLE) return; begin = self->last_switch_times[cpu]; end = event->tracepoint.time; if (begin == 0) goto finish; if (self->started_at == 0 || end < self->started_at) goto finish; if (self->ended_at != 0 && begin > self->ended_at) goto finish; raw = event->tracepoint.raw; memcpy (&prev_pid, raw + self->prev_pid_offset, sizeof prev_pid); memcpy (&prev_state, raw + self->prev_state_offset, sizeof prev_state); memcpy (&prev_comm[0], raw + self->prev_comm_offset, sizeof prev_comm); prev_comm[sizeof prev_comm-1] = 0; /* Ignore the kswapper events because they just clutter up the * timeline from things that would otherwise stand out. */ if (memcmp (prev_comm, "swapper/", strlen ("swapper/")) == 0) goto finish; g_snprintf (name, sizeof name, "CPU %u", cpu); sysprof_capture_writer_add_mark (self->recording->writer, begin, cpu, event->tracepoint.pid, end - begin, "Scheduler", name, prev_comm); finish: self->last_switch_times[cpu] = end; } static void sysprof_scheduler_details_parse_field (SysprofSchedulerDetails *self, char **parts) { G_GNUC_UNUSED gboolean _signed = FALSE; gint64 offset = -1; gint64 size = 0; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (parts != NULL); for (guint i = 1; parts[i]; i++) { if (g_str_has_prefix (parts[i], "offset:")) offset = g_ascii_strtoll (parts[i] + strlen ("offset:"), NULL, 10); else if (g_str_has_prefix (parts[i], "size:")) size = g_ascii_strtoll (parts[i] + strlen ("size:"), NULL, 10); else if (g_str_has_prefix (parts[i], "signed:")) _signed = parts[i][strlen ("signed:")] == '1'; } if (offset == -1) return; if (g_str_equal (parts[0], "field:char prev_comm[16];")) { if (size == 16) self->prev_comm_offset = offset; } else if (g_str_equal (parts[0], "field:pid_t prev_pid;")) { if (size == 4) self->prev_pid_offset = offset; } else if (g_str_equal (parts[0], "field:long prev_state;")) { if (size == sizeof (glong)) self->prev_state_offset = offset; } } static gboolean sysprof_scheduler_details_parse_format (SysprofSchedulerDetails *self, char *format) { LineReader reader; char *line; gsize line_len; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (format != NULL); line_reader_init (&reader, format, -1); while ((line = line_reader_next (&reader, &line_len))) { line[line_len] = 0; if (g_str_has_prefix (line, "ID: ")) { if (!(self->sched_switch_id = g_ascii_strtoll (line + strlen ("ID: "), NULL, 10))) return FALSE; } if (g_str_has_prefix (line, "\t")) { g_auto(GStrv) parts = g_strsplit (g_strstrip (line), "\t", 0); sysprof_scheduler_details_parse_field (self, parts); } } return self->sched_switch_id > 0 && self->prev_comm_offset > 0 && self->prev_state_offset > 0 && self->prev_pid_offset > 0; } static DexFuture * sysprof_scheduler_details_prepare_fiber (gpointer user_data) { SysprofSchedulerDetails *self = user_data; g_autoptr(GDBusConnection) bus = NULL; g_autoptr(GPtrArray) futures = NULL; g_autoptr(GVariant) format_reply = NULL; g_autoptr(GError) error = NULL; g_autofree char *format = NULL; int n_cpu; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (SYSPROF_IS_RECORDING (self->recording)); if (!(bus = dex_await_object (dex_bus_get (G_BUS_TYPE_SYSTEM), &error))) goto handle_error; if (!(format_reply = dex_await_variant (dex_dbus_connection_call (bus, "org.gnome.Sysprof3", "/org/gnome/Sysprof3", "org.gnome.Sysprof3.Service", "GetProcFile", g_variant_new ("(^ay)", "/sys/kernel/debug/tracing/events/sched/sched_switch/format"), G_VARIANT_TYPE ("(ay)"), G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION, G_MAXINT), &error))) goto handle_error; g_variant_get (format_reply, "(^ay)", &format); if (!sysprof_scheduler_details_parse_format (self, format)) { error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Could not parse data in tracepoint format"); goto handle_error; } futures = g_ptr_array_new_with_free_func (dex_unref); n_cpu = g_get_num_processors (); self->last_switch_times = g_new0 (gint64, n_cpu); for (int cpu = 0; cpu < n_cpu; cpu++) { struct perf_event_attr attr = {0}; attr.type = PERF_TYPE_TRACEPOINT; attr.type = PERF_TYPE_TRACEPOINT; attr.sample_type = PERF_SAMPLE_RAW | PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_IDENTIFIER | PERF_SAMPLE_TIME; attr.config = self->sched_switch_id; attr.sample_period = 1; #ifdef HAVE_PERF_CLOCKID attr.clockid = sysprof_clock; attr.use_clockid = 1; #endif attr.size = sizeof attr; g_ptr_array_add (futures, sysprof_perf_event_stream_new (bus, &attr, cpu, -1, 0, sysprof_scheduler_details_event_cb, self, NULL)); } /* Create perf stream per-cpu to get sched:sched_switch */ if (!dex_await (dex_future_allv ((DexFuture **)futures->pdata, futures->len), &error)) goto handle_error; for (int i = 0; i < futures->len; i++) { DexFuture *future = g_ptr_array_index (futures, i); g_autoptr(SysprofPerfEventStream) stream = dex_await_object (dex_ref (future), NULL); g_assert (stream != NULL); g_assert (SYSPROF_IS_PERF_EVENT_STREAM (stream)); /* We'll start processing these once start_time is set * for recording. That way we don't miss anything when * racing to record. */ if (sysprof_perf_event_stream_enable (stream, NULL)) g_ptr_array_add (self->streams, g_object_ref (stream)); } return dex_future_new_for_boolean (TRUE); handle_error: g_assert (error != NULL); _sysprof_recording_diagnostic (self->recording, "Scheduler", "Failed to register scheduler tracepoints: %s", error->message); return dex_future_new_for_error (g_steal_pointer (&error)); } static DexFuture * sysprof_scheduler_details_prepare (SysprofInstrument *instrument, SysprofRecording *recording) { SysprofSchedulerDetails *self = (SysprofSchedulerDetails *)instrument; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (SYSPROF_IS_RECORDING (recording)); g_set_object (&self->recording, recording); return dex_scheduler_spawn (NULL, 0, sysprof_scheduler_details_prepare_fiber, g_object_ref (self), g_object_unref); } static DexFuture * sysprof_scheduler_details_record_fiber (gpointer user_data) { SysprofSchedulerDetails *self = user_data; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (SYSPROF_IS_RECORDING (self->recording)); g_assert (DEX_IS_FUTURE (self->cancellable)); self->started_at = SYSPROF_CAPTURE_CURRENT_TIME; dex_await (dex_ref (self->cancellable), NULL); self->ended_at = SYSPROF_CAPTURE_CURRENT_TIME; for (guint i = 0; i < self->streams->len; i++) { SysprofPerfEventStream *stream = g_ptr_array_index (self->streams, i); sysprof_perf_event_stream_disable (stream, NULL); } if (self->streams->len) g_ptr_array_remove_range (self->streams, 0, self->streams->len); return dex_future_new_for_boolean (TRUE); } static DexFuture * sysprof_scheduler_details_record (SysprofInstrument *instrument, SysprofRecording *recording, GCancellable *cancellable) { SysprofSchedulerDetails *self = (SysprofSchedulerDetails *)instrument; g_assert (SYSPROF_IS_SCHEDULER_DETAILS (self)); g_assert (SYSPROF_IS_RECORDING (recording)); g_assert (recording == self->recording); g_assert (self->cancellable == NULL); self->cancellable = dex_cancellable_new_from_cancellable (cancellable); return dex_scheduler_spawn (NULL, 0, sysprof_scheduler_details_record_fiber, g_object_ref (self), g_object_unref); } static void sysprof_scheduler_details_dispose (GObject *object) { SysprofSchedulerDetails *self = (SysprofSchedulerDetails *)object; if (self->streams != NULL) { for (guint i = 0; i < self->streams->len; i++) { SysprofPerfEventStream *stream = g_ptr_array_index (self->streams, i); sysprof_perf_event_stream_disable (stream, NULL); } g_clear_pointer (&self->streams, g_ptr_array_unref); } g_clear_object (&self->recording); g_clear_pointer (&self->last_switch_times, g_free); dex_clear (&self->cancellable); G_OBJECT_CLASS (sysprof_scheduler_details_parent_class)->dispose (object); } static void sysprof_scheduler_details_class_init (SysprofSchedulerDetailsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); SysprofInstrumentClass *instrument_class = SYSPROF_INSTRUMENT_CLASS (klass); object_class->dispose = sysprof_scheduler_details_dispose; instrument_class->prepare = sysprof_scheduler_details_prepare; instrument_class->record = sysprof_scheduler_details_record; } static void sysprof_scheduler_details_init (SysprofSchedulerDetails *self) { self->sched_switch_id = -1; self->prev_comm_offset = -1; self->prev_pid_offset = -1; self->prev_state_offset = -1; self->streams = g_ptr_array_new_with_free_func (g_object_unref); } SysprofInstrument * sysprof_scheduler_details_new (void) { return g_object_new (SYSPROF_TYPE_SCHEDULER_DETAILS, NULL); }