mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
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.
414 lines
13 KiB
C
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);
|
|
}
|