mirror of
https://github.com/varun-r-mallya/sysprof.git
synced 2025-12-31 20:36:25 +00:00
libsysprof-gtk: add shared scheduler for workers
This is a scheduler that I put into GtkSourceView as well so that we can do multiple work items with a shared deadline.
This commit is contained in:
@ -70,6 +70,7 @@ libsysprof_gtk_private_sources = [
|
||||
'sysprof-mark-chart-item.c',
|
||||
'sysprof-mark-chart-row.c',
|
||||
'sysprof-progress-cell.c',
|
||||
'sysprof-scheduler.c',
|
||||
'sysprof-session-discover.c',
|
||||
'sysprof-symbol-label.c',
|
||||
]
|
||||
|
||||
49
src/libsysprof-gtk/sysprof-scheduler-private.h
Normal file
49
src/libsysprof-gtk/sysprof-scheduler-private.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* sysprof-scheduler-private.h
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef gboolean (*SysprofSchedulerCallback) (gint64 deadline,
|
||||
gpointer user_data);
|
||||
|
||||
gsize sysprof_scheduler_add (SysprofSchedulerCallback callback,
|
||||
gpointer user_data);
|
||||
gsize sysprof_scheduler_add_full (SysprofSchedulerCallback callback,
|
||||
gpointer user_data,
|
||||
GDestroyNotify notify);
|
||||
void sysprof_scheduler_remove (gsize handler_id);
|
||||
|
||||
static inline void
|
||||
sysprof_scheduler_clear (gsize *handler_id_ptr)
|
||||
{
|
||||
gsize val = *handler_id_ptr;
|
||||
|
||||
if (val)
|
||||
{
|
||||
*handler_id_ptr = 0;
|
||||
sysprof_scheduler_remove (val);
|
||||
}
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
278
src/libsysprof-gtk/sysprof-scheduler.c
Normal file
278
src/libsysprof-gtk/sysprof-scheduler.c
Normal file
@ -0,0 +1,278 @@
|
||||
/* sysprof-scheduler.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 <gtk/gtk.h>
|
||||
|
||||
#include "sysprof-scheduler-private.h"
|
||||
|
||||
/* The goal of this GSource is to let us pile on a bunch of background work but
|
||||
* only do a small amount of it at a time per-frame cycle. This becomes more
|
||||
* important when you have multiple documents open all competing to do
|
||||
* background work and potentially stalling the main loop.
|
||||
*
|
||||
* Instead, we only do 1 msec of work at a time and then wait for the next
|
||||
* frame to come in.
|
||||
*
|
||||
* Since we don't have access to the widgets, we need to base this work off the
|
||||
* shortest delay between frames among the available monitors. Some aliasing is
|
||||
* still possible depending on per-monitor scan-outs, but we already have that
|
||||
* issue without this.
|
||||
*/
|
||||
|
||||
#define _1_MSEC (G_USEC_PER_SEC / 1000L)
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GSource source;
|
||||
GQueue queue;
|
||||
gint64 interval;
|
||||
gsize last_handler_id;
|
||||
} SysprofScheduler;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GList link;
|
||||
SysprofSchedulerCallback callback;
|
||||
gpointer user_data;
|
||||
GDestroyNotify notify;
|
||||
gint64 ready_time;
|
||||
gsize id;
|
||||
} SysprofTask;
|
||||
|
||||
static GSource *the_source;
|
||||
|
||||
static void
|
||||
sysprof_task_free (SysprofTask *task)
|
||||
{
|
||||
g_assert (task != NULL);
|
||||
g_assert (task->link.data == (gpointer)task);
|
||||
g_assert (task->link.next == NULL);
|
||||
g_assert (task->link.prev == NULL);
|
||||
g_assert (task->callback != NULL);
|
||||
|
||||
if (task->notify != NULL)
|
||||
task->notify (task->user_data);
|
||||
|
||||
g_slice_free (SysprofTask, task);
|
||||
}
|
||||
|
||||
static SysprofTask *
|
||||
sysprof_task_new (SysprofSchedulerCallback callback,
|
||||
gpointer user_data,
|
||||
GDestroyNotify notify)
|
||||
{
|
||||
SysprofTask *task;
|
||||
|
||||
g_return_val_if_fail (callback != NULL, NULL);
|
||||
|
||||
task = g_slice_new0 (SysprofTask);
|
||||
task->link.data = task;
|
||||
task->callback = callback;
|
||||
task->user_data = user_data;
|
||||
task->notify = notify;
|
||||
task->ready_time = 0; /* Now */
|
||||
task->id = 0;
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
static gint64
|
||||
get_interval (SysprofScheduler *self)
|
||||
{
|
||||
if G_UNLIKELY (self->interval == 0)
|
||||
{
|
||||
GdkDisplay *display = gdk_display_get_default ();
|
||||
GListModel *monitors = gdk_display_get_monitors (display);
|
||||
guint n_items = g_list_model_get_n_items (monitors);
|
||||
gint64 lowest_interval = 60000;
|
||||
|
||||
for (guint i = 0; i < n_items; i++)
|
||||
{
|
||||
GdkMonitor *monitor = g_list_model_get_item (monitors, i);
|
||||
gint64 interval = gdk_monitor_get_refresh_rate (monitor);
|
||||
|
||||
if (interval != 0 && interval < lowest_interval)
|
||||
lowest_interval = interval;
|
||||
|
||||
g_object_unref (monitor);
|
||||
}
|
||||
|
||||
self->interval = (double)G_USEC_PER_SEC / (double)lowest_interval * 1000.0;
|
||||
}
|
||||
|
||||
return self->interval;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysprof_scheduler_prepare (GSource *source,
|
||||
int *timeout)
|
||||
{
|
||||
*timeout = -1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysprof_scheduler_check (GSource *source)
|
||||
{
|
||||
SysprofScheduler *self = (SysprofScheduler *)source;
|
||||
SysprofTask *task = g_queue_peek_head (&self->queue);
|
||||
|
||||
return task != NULL && task->ready_time <= g_source_get_time (source);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
sysprof_scheduler_dispatch (GSource *source,
|
||||
GSourceFunc source_func,
|
||||
gpointer user_data)
|
||||
{
|
||||
SysprofScheduler *self = (SysprofScheduler *)source;
|
||||
gint64 current = g_source_get_time (source);
|
||||
gint64 deadline = current + _1_MSEC;
|
||||
gint64 interval = get_interval (self);
|
||||
|
||||
/* Try to process as many items within our quanta if they */
|
||||
while (g_get_monotonic_time () < deadline)
|
||||
{
|
||||
SysprofTask *task = g_queue_peek_head (&self->queue);
|
||||
|
||||
if (task == NULL)
|
||||
break;
|
||||
|
||||
g_queue_unlink (&self->queue, &task->link);
|
||||
|
||||
if (task->callback (deadline, task->user_data))
|
||||
{
|
||||
task->ready_time = current + interval;
|
||||
g_queue_push_tail_link (&self->queue, &task->link);
|
||||
break;
|
||||
}
|
||||
|
||||
sysprof_task_free (task);
|
||||
}
|
||||
|
||||
if (self->queue.head != NULL)
|
||||
{
|
||||
SysprofTask *task = g_queue_peek_head (&self->queue);
|
||||
g_source_set_ready_time (source, task->ready_time);
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static void
|
||||
sysprof_scheduler_finalize (GSource *source)
|
||||
{
|
||||
#ifndef G_DISABLE_ASSERT
|
||||
SysprofScheduler *self = (SysprofScheduler *)source;
|
||||
|
||||
g_assert (self->queue.length == 0);
|
||||
g_assert (self->queue.head == NULL);
|
||||
g_assert (self->queue.tail == NULL);
|
||||
#endif
|
||||
|
||||
if (source == the_source)
|
||||
the_source = NULL;
|
||||
}
|
||||
|
||||
static GSourceFuncs source_funcs = {
|
||||
sysprof_scheduler_prepare,
|
||||
sysprof_scheduler_check,
|
||||
sysprof_scheduler_dispatch,
|
||||
sysprof_scheduler_finalize,
|
||||
};
|
||||
|
||||
static SysprofScheduler *
|
||||
get_scheduler (void)
|
||||
{
|
||||
if (the_source == NULL)
|
||||
{
|
||||
the_source = g_source_new (&source_funcs, sizeof (SysprofScheduler));
|
||||
g_source_set_name (the_source, "SysprofScheduler");
|
||||
g_source_set_priority (the_source, G_PRIORITY_LOW);
|
||||
g_source_set_ready_time (the_source, 0);
|
||||
g_source_attach (the_source, g_main_context_default ());
|
||||
g_source_unref (the_source);
|
||||
}
|
||||
|
||||
return (SysprofScheduler *)the_source;
|
||||
}
|
||||
|
||||
gsize
|
||||
sysprof_scheduler_add (SysprofSchedulerCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
return sysprof_scheduler_add_full (callback, user_data, NULL);
|
||||
}
|
||||
|
||||
gsize
|
||||
sysprof_scheduler_add_full (SysprofSchedulerCallback callback,
|
||||
gpointer user_data,
|
||||
GDestroyNotify notify)
|
||||
{
|
||||
SysprofScheduler *self;
|
||||
SysprofTask *task;
|
||||
|
||||
g_return_val_if_fail (callback != NULL, 0);
|
||||
|
||||
self = get_scheduler ();
|
||||
task = sysprof_task_new (callback, user_data, notify);
|
||||
task->id = ++self->last_handler_id;
|
||||
|
||||
/* Request progress immediately */
|
||||
g_queue_push_head_link (&self->queue, &task->link);
|
||||
g_source_set_ready_time ((GSource *)self, g_source_get_time ((GSource *)self));
|
||||
|
||||
return task->id;
|
||||
}
|
||||
|
||||
void
|
||||
sysprof_scheduler_remove (gsize handler_id)
|
||||
{
|
||||
SysprofScheduler *self;
|
||||
|
||||
g_return_if_fail (handler_id != 0);
|
||||
|
||||
self = get_scheduler ();
|
||||
|
||||
for (const GList *iter = self->queue.head; iter != NULL; iter = iter->next)
|
||||
{
|
||||
SysprofTask *task = iter->data;
|
||||
|
||||
if (task->id == handler_id)
|
||||
{
|
||||
g_queue_unlink (&self->queue, &task->link);
|
||||
sysprof_task_free (task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->queue.head != NULL)
|
||||
{
|
||||
SysprofTask *task = g_queue_peek_head (&self->queue);
|
||||
g_source_set_ready_time ((GSource *)self, task->ready_time);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_source_destroy ((GSource *)self);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user