Files
sysprof/src/libsysprof/sysprof-scheduler-details.c
Christian Hergert 5dc4e83307 libsysprof: ignore swapper/# scheduler details
We "know" that the swapper runs between each process inherently so no need
to really include that in the scheduler details. It just clutters up the
event timeline. Without it, we're more likely to see patterns in the
scribbles.
2023-09-07 13:18:41 -07:00

414 lines
13 KiB
C

/* sysprof-scheduler-details.c
*
* Copyright 2023 Christian Hergert <chergert@redhat.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
* 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);
}