Merge branch 'kate/replace-backend' into 'main'

gio: gmemorymonitorpsi: Replace GMemoryMonitor backend with kernel PSI event

See merge request GNOME/glib!4481
This commit is contained in:
Philip Withnall
2025-07-21 17:32:21 +00:00
12 changed files with 1370 additions and 0 deletions

View File

@@ -52,6 +52,12 @@
#include "gmemorymonitor.h"
#include "gmemorymonitorportal.h"
#include "gmemorymonitordbus.h"
#ifdef __linux__
#include "gmemorymonitorpsi.h"
#endif
#ifdef HAVE_SYSINFO
#include "gmemorymonitorpoll.h"
#endif
#include "gpowerprofilemonitor.h"
#include "gpowerprofilemonitordbus.h"
#include "gpowerprofilemonitorportal.h"
@@ -1081,6 +1087,12 @@ extern GType _g_network_monitor_nm_get_type (void);
extern GType g_debug_controller_dbus_get_type (void);
extern GType g_memory_monitor_dbus_get_type (void);
#ifdef __linux__
extern GType g_memory_monitor_psi_get_type (void);
#endif
#ifdef HAVE_SYSINFO
extern GType g_memory_monitor_poll_get_type (void);
#endif
extern GType g_memory_monitor_portal_get_type (void);
extern GType g_memory_monitor_win32_get_type (void);
extern GType g_power_profile_monitor_dbus_get_type (void);
@@ -1361,6 +1373,12 @@ _g_io_modules_ensure_loaded (void)
g_type_ensure (g_gtk_notification_backend_get_type ());
g_type_ensure (g_portal_notification_backend_get_type ());
g_type_ensure (g_memory_monitor_dbus_get_type ());
#ifdef __linux__
g_type_ensure (g_memory_monitor_psi_get_type ());
#endif
#ifdef HAVE_SYSINFO
g_type_ensure (g_memory_monitor_poll_get_type ());
#endif
g_type_ensure (g_memory_monitor_portal_get_type ());
g_type_ensure (g_network_monitor_portal_get_type ());
g_type_ensure (g_power_profile_monitor_portal_get_type ());

153
gio/gmemorymonitorbase.c Normal file
View File

@@ -0,0 +1,153 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gcancellable.h"
#include "ginitable.h"
#include "gioerror.h"
#include "giomodule-priv.h"
#include "glib/gstdio.h"
#include "glibintl.h"
#include "gmemorymonitor.h"
#include "gmemorymonitorbase.h"
#ifdef HAVE_SYSINFO
#include <sys/sysinfo.h>
#endif
/**
* GMemoryMonitorBase:
*
* An abstract base class for implementations of [iface@Gio.MemoryMonitor] which
* provides several defined warning levels (`GLowMemoryLevel`) and tracks how
* often they are notified to the user via [signal@Gio.MemoryMonitor::low-memory-warning]
* to limit the number of signal emissions to one every 15 seconds for each level.
* [method@Gio.MemoryMonitorBase.send_event_to_user] is provided for this purpose.
*/
/* The interval between sending a signal in second */
#define RECOVERY_INTERVAL_SEC 15
#define G_MEMORY_MONITOR_BASE_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
static void g_memory_monitor_base_iface_init (GMemoryMonitorInterface *iface);
static void g_memory_monitor_base_initable_iface_init (GInitableIface *iface);
typedef struct
{
GObject parent_instance;
guint64 last_trigger_us[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT];
} GMemoryMonitorBasePrivate;
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GMemoryMonitorBase, g_memory_monitor_base, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_memory_monitor_base_initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
g_memory_monitor_base_iface_init)
G_ADD_PRIVATE (GMemoryMonitorBase))
gdouble
g_memory_monitor_base_query_mem_ratio (void)
{
#ifdef HAVE_SYSINFO
struct sysinfo info;
if (sysinfo (&info))
return -1.0;
if (info.totalram == 0)
return -1.0;
return (gdouble) ((gdouble) info.freeram / (gdouble) info.totalram);
#else
return -1.0;
#endif
}
GMemoryMonitorWarningLevel
g_memory_monitor_base_level_enum_to_byte (GMemoryMonitorLowMemoryLevel level)
{
const GMemoryMonitorWarningLevel level_bytes[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT] = {
[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW] = 50,
[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM] = 100,
[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL] = 255
};
if ((int) level < G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID ||
(int) level >= G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT)
g_assert_not_reached ();
if (level == G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID)
return 0;
return level_bytes[level];
}
void
g_memory_monitor_base_send_event_to_user (GMemoryMonitorBase *monitor,
GMemoryMonitorLowMemoryLevel warning_level)
{
gint64 current_time;
GMemoryMonitorBasePrivate *priv = g_memory_monitor_base_get_instance_private (monitor);
current_time = g_get_monotonic_time ();
if (priv->last_trigger_us[warning_level] == 0 ||
(current_time - priv->last_trigger_us[warning_level]) > (RECOVERY_INTERVAL_SEC * G_USEC_PER_SEC))
{
g_debug ("Send low memory signal with warning level %u", warning_level);
g_signal_emit_by_name (monitor, "low-memory-warning",
g_memory_monitor_base_level_enum_to_byte (warning_level));
priv->last_trigger_us[warning_level] = current_time;
}
}
static gboolean
g_memory_monitor_base_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
return TRUE;
}
static void
g_memory_monitor_base_init (GMemoryMonitorBase *monitor)
{
}
static void
g_memory_monitor_base_class_init (GMemoryMonitorBaseClass *klass)
{
}
static void
g_memory_monitor_base_iface_init (GMemoryMonitorInterface *monitor_iface)
{
}
static void
g_memory_monitor_base_initable_iface_init (GInitableIface *iface)
{
iface->init = g_memory_monitor_base_initable_init;
}

53
gio/gmemorymonitorbase.h Normal file
View File

@@ -0,0 +1,53 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __G_MEMORY_MONITOR_BASE_H__
#define __G_MEMORY_MONITOR_BASE_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define G_TYPE_MEMORY_MONITOR_BASE (g_memory_monitor_base_get_type ())
G_DECLARE_DERIVABLE_TYPE (GMemoryMonitorBase, g_memory_monitor_base, G, MEMORY_MONITOR_BASE, GObject)
typedef enum
{
G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID = -1,
G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW = 0,
G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM,
G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL,
G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT
} GMemoryMonitorLowMemoryLevel;
struct _GMemoryMonitorBaseClass
{
GObjectClass parent_class;
};
void g_memory_monitor_base_send_event_to_user (GMemoryMonitorBase *monitor,
GMemoryMonitorLowMemoryLevel warning_level);
GMemoryMonitorWarningLevel g_memory_monitor_base_level_enum_to_byte (GMemoryMonitorLowMemoryLevel level);
gdouble g_memory_monitor_base_query_mem_ratio (void);
G_END_DECLS
#endif /* __G_MEMORY_MONITOR_BASE_H__ */

273
gio/gmemorymonitorpoll.c Normal file
View File

@@ -0,0 +1,273 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gcancellable.h"
#include "gdbusnamewatching.h"
#include "gdbusproxy.h"
#include "ginitable.h"
#include "gioerror.h"
#include "giomodule-priv.h"
#include "glib/gstdio.h"
#include "glib/glib-private.h"
#include "glibintl.h"
#include "gmemorymonitor.h"
#include "gmemorymonitorpoll.h"
#include <fcntl.h>
#include <unistd.h>
/**
* GMemoryMonitorPoll:
*
* A [iface@Gio.MemoryMonitor] which polls the system free/used
* memory ratio on a fixed timer.
*
* It polls, for example, every 10 seconds, and emits different
* [signal@Gio.MemoryMonitor::low-memory-warning] signals if it falls below several
* low thresholds.
*
* The system free/used memory ratio is queried using [`sysinfo()`](man:sysinfo(2)).
*
* This is intended as a fallback implementation of [iface@Gio.MemoryMonitor] in case
* other, more performant, implementations are not supported on the system.
*
* Since: 2.86
*/
typedef enum {
PROP_MEM_FREE_RATIO = 1,
PROP_POLL_INTERVAL_MS,
} GMemoryMonitorPollProperty;
#define G_MEMORY_MONITOR_POLL_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
static void g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *iface);
static void g_memory_monitor_poll_initable_iface_init (GInitableIface *iface);
struct _GMemoryMonitorPoll
{
GMemoryMonitorBase parent_instance;
GMainContext *worker; /* (unowned) */
GSource *source_timeout; /* (owned) */
/* it overrides the default timeout when running the test */
unsigned int poll_interval_ms; /* zero to use the default */
gdouble mem_free_ratio;
};
/* the default monitor timeout */
#define G_MEMORY_MONITOR_PSI_DEFAULT_SEC 10
G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPoll, g_memory_monitor_poll, G_TYPE_MEMORY_MONITOR_BASE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_memory_monitor_poll_initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
g_memory_monitor_poll_iface_init)
_g_io_modules_ensure_extension_points_registered ();
g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
g_define_type_id,
"poll",
10))
static void
g_memory_monitor_poll_init (GMemoryMonitorPoll *mem_poll)
{
mem_poll->mem_free_ratio = -1.0;
}
static void
g_memory_monitor_poll_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
switch ((GMemoryMonitorPollProperty) prop_id)
{
case PROP_MEM_FREE_RATIO:
monitor->mem_free_ratio = g_value_get_double (value);
break;
case PROP_POLL_INTERVAL_MS:
monitor->poll_interval_ms = g_value_get_uint (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
g_memory_monitor_poll_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
switch ((GMemoryMonitorPollProperty) prop_id)
{
case PROP_MEM_FREE_RATIO:
g_value_set_double (value, monitor->mem_free_ratio);
break;
case PROP_POLL_INTERVAL_MS:
g_value_set_uint (value, monitor->poll_interval_ms);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
g_memory_monitor_mem_ratio_cb (gpointer data)
{
GMemoryMonitorPoll *monitor = (GMemoryMonitorPoll *) data;
gdouble mem_ratio;
GMemoryMonitorLowMemoryLevel warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID;
/* Should be executed in the worker context */
g_assert (g_main_context_is_owner (monitor->worker));
mem_ratio = g_memory_monitor_base_query_mem_ratio ();
if (mem_ratio < 0.0)
return G_SOURCE_REMOVE;
if (mem_ratio > 0.5)
return G_SOURCE_CONTINUE;
/* free ratio override */
if (monitor->mem_free_ratio >= 0.0)
mem_ratio = monitor->mem_free_ratio;
g_debug ("memory free ratio %f", mem_ratio);
if (mem_ratio < 0.2)
warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL;
else if (mem_ratio < 0.3)
warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM;
else if (mem_ratio < 0.4)
warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW;
if (warning_level != G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID)
g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), warning_level);
return G_SOURCE_CONTINUE;
}
static gboolean
g_memory_monitor_poll_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (initable);
GSource *source;
if (monitor->poll_interval_ms > 0)
{
if (monitor->poll_interval_ms < G_TIME_SPAN_MILLISECOND)
source = g_timeout_source_new (monitor->poll_interval_ms);
else
source = g_timeout_source_new_seconds (monitor->poll_interval_ms / G_TIME_SPAN_MILLISECOND);
}
else
{
/* default 10 second */
source = g_timeout_source_new_seconds (G_MEMORY_MONITOR_PSI_DEFAULT_SEC);
}
g_source_set_callback (source, g_memory_monitor_mem_ratio_cb, monitor, NULL);
monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
g_source_attach (source, monitor->worker);
monitor->source_timeout = g_steal_pointer (&source);
return TRUE;
}
static void
g_memory_monitor_poll_finalize (GObject *object)
{
GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
g_source_destroy (monitor->source_timeout);
g_source_unref (monitor->source_timeout);
G_OBJECT_CLASS (g_memory_monitor_poll_parent_class)->finalize (object);
}
static void
g_memory_monitor_poll_class_init (GMemoryMonitorPollClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = g_memory_monitor_poll_set_property;
object_class->get_property = g_memory_monitor_poll_get_property;
object_class->finalize = g_memory_monitor_poll_finalize;
/**
* GMemoryMonitorPoll:mem-free-ratio:
*
* Override the memory free ratio
*
* Since: 2.86
*/
g_object_class_install_property (object_class,
PROP_MEM_FREE_RATIO,
g_param_spec_double ("mem-free-ratio", NULL, NULL,
-1.0, 1.0,
-1.0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* GMemoryMonitorPoll:poll-interval-ms:
*
* Override the poll interval for monitoring the memory usage.
*
* The interval is in milliseconds. Zero means to use the default interval.
*
* Since: 2.86
*/
g_object_class_install_property (object_class,
PROP_POLL_INTERVAL_MS,
g_param_spec_uint ("poll-interval-ms", NULL, NULL,
0, G_MAXUINT,
0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static void
g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *monitor_iface)
{
}
static void
g_memory_monitor_poll_initable_iface_init (GInitableIface *iface)
{
iface->init = g_memory_monitor_poll_initable_init;
}

38
gio/gmemorymonitorpoll.h Normal file
View File

@@ -0,0 +1,38 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __G_MEMORY_MONITOR_POLL_H__
#define __G_MEMORY_MONITOR_POLL_H__
#include "gmemorymonitorbase.h"
#include <glib-object.h>
G_BEGIN_DECLS
#define G_TYPE_MEMORY_MONITOR_POLL (g_memory_monitor_poll_get_type ())
G_DECLARE_FINAL_TYPE (GMemoryMonitorPoll, g_memory_monitor_poll, G, MEMORY_MONITOR_POLL, GMemoryMonitorBase)
GIO_AVAILABLE_IN_2_86
GType g_memory_monitor_poll_get_type (void);
G_END_DECLS
#endif /* __G_MEMORY_MONITOR_PSI_H__ */

512
gio/gmemorymonitorpsi.c Normal file
View File

@@ -0,0 +1,512 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gcancellable.h"
#include "gdbusnamewatching.h"
#include "gdbusproxy.h"
#include "ginitable.h"
#include "gioerror.h"
#include "giomodule-priv.h"
#include "glibintl.h"
#include "glib/glib-private.h"
#include "glib/gstdio.h"
#include "gmemorymonitor.h"
#include "gmemorymonitorbase.h"
#include "gmemorymonitorpsi.h"
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
/**
* GMemoryMonitorPsi:
*
* A Linux [iface@Gio.MemoryMonitor] which uses the kernel
* [pressure stall information](https://www.kernel.org/doc/html/latest/accounting/psi.html) (PSI).
*
* When it receives a PSI event, it emits
* [signal@Gio.MemoryMonitor::low-memory-warning] with an appropriate warning
* level.
*
* Since: 2.86
*/
/* Unprivileged users can also create monitors, with
* the only limitation that the window size must be a
* `multiple of 2s`, in order to prevent excessive resource usage.
* see: https://www.kernel.org/doc/html/latest/accounting/psi.html*/
#define PSI_WINDOW_SEC 2
typedef enum {
PROP_PROC_PATH = 1,
} GMemoryMonitorPsiProperty;
typedef enum
{
MEMORY_PRESSURE_MONITOR_TRIGGER_SOME,
MEMORY_PRESSURE_MONITOR_TRIGGER_FULL,
MEMORY_PRESSURE_MONITOR_TRIGGER_MFD
} MemoryPressureMonitorTriggerType;
/* Each trigger here results in an open fd for the lifetime
* of the `GMemoryMonitor`, so dont add too many */
static const struct
{
MemoryPressureMonitorTriggerType trigger_type;
int threshold_ms;
} triggers[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT] = {
{ MEMORY_PRESSURE_MONITOR_TRIGGER_SOME, 70 }, /* 70ms out of 2sec for partial stall */
{ MEMORY_PRESSURE_MONITOR_TRIGGER_SOME, 100 }, /* 100ms out of 2sec for partial stall */
{ MEMORY_PRESSURE_MONITOR_TRIGGER_FULL, 100 }, /* 100ms out of 2sec for complete stall */
};
typedef struct
{
GSource source;
GPollFD *pollfd;
GMemoryMonitorLowMemoryLevel level_type;
GWeakRef monitor_weak;
} MemoryMonitorSource;
typedef gboolean (*MemoryMonitorCallbackFunc) (GMemoryMonitorPsi *monitor,
GMemoryMonitorLowMemoryLevel level_type,
void *user_data);
#define G_MEMORY_MONITOR_PSI_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
static void g_memory_monitor_psi_iface_init (GMemoryMonitorInterface *iface);
static void g_memory_monitor_psi_initable_iface_init (GInitableIface *iface);
struct _GMemoryMonitorPsi
{
GMemoryMonitorBase parent_instance;
GMainContext *worker; /* (unowned) */
GSource *triggers[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT]; /* (owned) (nullable) */
char *cg_path;
char *proc_path;
gboolean proc_override;
};
G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPsi, g_memory_monitor_psi, G_TYPE_MEMORY_MONITOR_BASE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
g_memory_monitor_psi_initable_iface_init)
G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
g_memory_monitor_psi_iface_init)
_g_io_modules_ensure_extension_points_registered ();
g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
g_define_type_id,
"psi",
20))
static void
g_memory_monitor_psi_init (GMemoryMonitorPsi *psi)
{
}
static void
g_memory_monitor_psi_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
switch ((GMemoryMonitorPsiProperty) prop_id)
{
case PROP_PROC_PATH:
/* Construct only */
g_assert (monitor->proc_path == NULL);
monitor->proc_path = g_value_dup_string (value);
if (monitor->proc_path != NULL)
monitor->proc_override = TRUE;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
g_memory_monitor_psi_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
switch ((GMemoryMonitorPsiProperty) prop_id)
{
case PROP_PROC_PATH:
g_value_set_string (value, monitor->proc_override ? monitor->proc_path : NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static gboolean
g_memory_monitor_low_trigger_cb (GMemoryMonitorPsi *monitor,
GMemoryMonitorLowMemoryLevel level_type,
void *user_data)
{
gdouble mem_ratio;
/* Should be executed in the worker context */
g_assert (g_main_context_is_owner (monitor->worker));
mem_ratio = g_memory_monitor_base_query_mem_ratio ();
/* if mem free ratio > 0.5, don't signal */
if (mem_ratio < 0.0)
return G_SOURCE_REMOVE;
else if (mem_ratio > 0.5)
return G_SOURCE_CONTINUE;
g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), level_type);
return G_SOURCE_CONTINUE;
}
static gboolean
event_check (GSource *source)
{
MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
if (ev_source->pollfd->revents)
return G_SOURCE_CONTINUE;
return G_SOURCE_REMOVE;
}
static gboolean
event_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
GMemoryMonitorPsi *monitor = NULL;
monitor = g_weak_ref_get (&ev_source->monitor_weak);
if (monitor == NULL)
return G_SOURCE_REMOVE;
if (monitor->proc_override)
{
if (!(g_source_query_unix_fd (source, ev_source->pollfd) & G_IO_IN))
{
g_object_unref (monitor);
return G_SOURCE_CONTINUE;
}
}
else
{
if (!(g_source_query_unix_fd (source, ev_source->pollfd) & G_IO_PRI))
{
g_object_unref (monitor);
return G_SOURCE_CONTINUE;
}
}
if (callback)
((MemoryMonitorCallbackFunc) callback) (monitor, ev_source->level_type, user_data);
g_object_unref (monitor);
return G_SOURCE_CONTINUE;
}
static void
event_finalize (GSource *source)
{
MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
g_weak_ref_clear (&ev_source->monitor_weak);
}
static GSourceFuncs memory_monitor_event_funcs = {
.check = event_check,
.dispatch = event_dispatch,
.finalize = event_finalize,
};
static GSource *
g_memory_monitor_create_source (GMemoryMonitorPsi *monitor,
int fd,
GMemoryMonitorLowMemoryLevel level_type,
gboolean is_path_override)
{
MemoryMonitorSource *source;
source = (MemoryMonitorSource *) g_source_new (&memory_monitor_event_funcs, sizeof (MemoryMonitorSource));
if (is_path_override)
source->pollfd = g_source_add_unix_fd ((GSource *) source, fd, G_IO_IN | G_IO_ERR);
else
source->pollfd = g_source_add_unix_fd ((GSource *) source, fd, G_IO_PRI | G_IO_ERR);
source->level_type = level_type;
g_weak_ref_init (&source->monitor_weak, monitor);
return (GSource *) source;
}
static gboolean
g_memory_monitor_psi_calculate_mem_pressure_path (GMemoryMonitorPsi *monitor,
GError **error)
{
pid_t pid;
gchar *path_read = NULL;
gchar *replacement = NULL;
GRegex *regex = NULL;
if (!monitor->proc_override)
{
pid = getpid ();
monitor->proc_path = g_strdup_printf ("/proc/%d/cgroup", pid);
}
if (!g_file_get_contents (monitor->proc_path, &path_read, NULL, error))
{
g_free (path_read);
return FALSE;
}
/* cgroupv2 is only supportted and the format is shown as follows:
* ex: 0::/user.slice/user-0.slice/session-c3.scope */
regex = g_regex_new ("^0::", G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT, error);
if (!g_regex_match (regex, path_read, G_REGEX_MATCH_DEFAULT, NULL))
{
g_debug ("Unsupported cgroup path information.");
g_free (path_read);
g_regex_unref (regex);
return FALSE;
}
/* drop "0::" */
replacement = g_regex_replace (regex, path_read,
-1, 0,
"", G_REGEX_MATCH_DEFAULT, error);
if (replacement == NULL)
{
g_debug ("Unsupported cgroup path format.");
g_free (path_read);
g_regex_unref (regex);
return FALSE;
}
replacement = g_strstrip (replacement);
if (monitor->proc_override)
{
monitor->cg_path = g_steal_pointer (&replacement);
g_free (path_read);
g_regex_unref (regex);
return TRUE;
}
monitor->cg_path = g_build_filename ("/sys/fs/cgroup", replacement, "memory.pressure", NULL);
g_debug ("cgroup path is %s", monitor->cg_path);
g_free (path_read);
g_free (replacement);
g_regex_unref (regex);
return g_file_test (monitor->cg_path, G_FILE_TEST_EXISTS);
}
static GSource *
g_memory_monitor_psi_setup_trigger (GMemoryMonitorPsi *monitor,
GMemoryMonitorLowMemoryLevel level_type,
int threshold_us,
int window_us,
GError **error)
{
GSource *source;
int fd;
int ret;
size_t wlen;
gchar *trigger = NULL;
fd = g_open (monitor->cg_path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
if (fd < 0)
{
int errsv = errno;
g_debug ("Error on opening %s: %s", monitor->cg_path, g_strerror (errsv));
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
"Error on opening %s: %s", monitor->cg_path, g_strerror (errsv));
return NULL;
}
/* The kernel PSI [1] trigger format is:
* <some|full> <stall amount in us> <time window in us>
* The “some” indicates the share of time in which at least some tasks are stalled on a given resource.
* The “full” indicates the share of time in which all non-idle tasks are stalled on a given resource simultaneously.
* "stall amount in us": The total stall time in us.
* "time window in us": The specific time window in us.
* e.g. writing "some 150000 1000000" would add 150ms threshold for partial memory stall measured within 1sec time window
*
* [1] https://docs.kernel.org/accounting/psi.html
*/
trigger = g_strdup_printf ("%s %d %d",
(triggers[level_type].trigger_type == MEMORY_PRESSURE_MONITOR_TRIGGER_SOME) ? "some" : "full",
threshold_us,
window_us);
errno = 0;
wlen = strlen (trigger) + 1;
while (wlen > 0)
{
int errsv;
g_debug ("Write trigger %s", trigger);
ret = write (fd, trigger, wlen);
errsv = errno;
if (ret < 0)
{
if (errsv == EINTR)
{
/* interrupted by signal, retry */
continue;
}
else
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
"Error on setting PSI configurations: %s",
g_strerror (errsv));
g_free (trigger);
close (fd);
return NULL;
}
}
wlen -= ret;
}
g_free (trigger);
source = g_memory_monitor_create_source (monitor, fd, level_type, monitor->proc_override);
g_source_set_callback (source, G_SOURCE_FUNC (g_memory_monitor_low_trigger_cb), NULL, NULL);
return g_steal_pointer (&source);
}
static gboolean
g_memory_monitor_setup_psi (GMemoryMonitorPsi *monitor,
GError **error)
{
if (!g_memory_monitor_psi_calculate_mem_pressure_path (monitor, error))
return FALSE;
for (size_t i = 0; i < G_N_ELEMENTS (triggers); i++)
{
/* the user defined PSI is estimated per second and the unit is in micro second(us). */
monitor->triggers[i] = g_memory_monitor_psi_setup_trigger (monitor,
i,
triggers[i].threshold_ms * 1000,
PSI_WINDOW_SEC * 1000 * 1000,
error);
if (monitor->triggers[i] == NULL)
return FALSE;
}
return TRUE;
}
static gboolean
g_memory_monitor_psi_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (initable);
monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
if (g_memory_monitor_setup_psi (monitor, error))
{
for (size_t i = 0; i < G_N_ELEMENTS (monitor->triggers); i++)
{
g_source_attach (monitor->triggers[i], monitor->worker);
}
}
else
{
g_debug ("PSI is not supported.");
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PSI is not supported.");
return FALSE;
}
return TRUE;
}
static void
g_memory_monitor_psi_finalize (GObject *object)
{
GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
g_free (monitor->cg_path);
g_free (monitor->proc_path);
for (size_t i = 0; i < G_N_ELEMENTS (monitor->triggers); i++)
{
g_source_destroy (monitor->triggers[i]);
g_source_unref (monitor->triggers[i]);
}
G_OBJECT_CLASS (g_memory_monitor_psi_parent_class)->finalize (object);
}
static void
g_memory_monitor_psi_class_init (GMemoryMonitorPsiClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = g_memory_monitor_psi_set_property;
object_class->get_property = g_memory_monitor_psi_get_property;
object_class->finalize = g_memory_monitor_psi_finalize;
/**
* GMemoryMonitorPsi:proc-path: (nullable)
*
* Kernel PSI path to use, if not the default.
*
* This is typically only used for test purposes.
*
* Since: 2.86
*/
g_object_class_install_property (object_class,
PROP_PROC_PATH,
g_param_spec_string ("proc-path", NULL, NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
}
static void
g_memory_monitor_psi_iface_init (GMemoryMonitorInterface *monitor_iface)
{
}
static void
g_memory_monitor_psi_initable_iface_init (GInitableIface *iface)
{
iface->init = g_memory_monitor_psi_initable_init;
}

38
gio/gmemorymonitorpsi.h Normal file
View File

@@ -0,0 +1,38 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __G_MEMORY_MONITOR_PSI_H__
#define __G_MEMORY_MONITOR_PSI_H__
#include "gmemorymonitorbase.h"
#include <glib-object.h>
G_BEGIN_DECLS
#define G_TYPE_MEMORY_MONITOR_PSI (g_memory_monitor_psi_get_type ())
G_DECLARE_FINAL_TYPE (GMemoryMonitorPsi, g_memory_monitor_psi, G, MEMORY_MONITOR_PSI, GMemoryMonitorBase)
GIO_AVAILABLE_IN_2_86
GType g_memory_monitor_psi_get_type (void);
G_END_DECLS
#endif /* __G_MEMORY_MONITOR_PSI_H__ */

View File

@@ -365,6 +365,7 @@ if host_system != 'windows'
'gunixoutputstream.c',
'gfdonotificationbackend.c',
'ggtknotificationbackend.c',
'gmemorymonitorpsi.c',
)
portal_sources = [files(
@@ -390,6 +391,12 @@ if host_system != 'windows'
'gunixoutputstream.h',
)
if glib_conf.has('HAVE_SYSINFO')
unix_sources += files(
'gmemorymonitorpoll.c',
)
endif
if glib_have_cocoa
settings_sources += files('gnextstepsettingsbackend.m')
contenttype_sources += files('gcontenttype-osx.m')
@@ -528,6 +535,7 @@ gio_base_sources = files(
'gmarshal-internal.c',
'gmount.c',
'gmemorymonitor.c',
'gmemorymonitorbase.c',
'gmemorymonitordbus.c',
'gmemoryinputstream.c',
'gmemoryoutputstream.c',

View File

@@ -0,0 +1,144 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include "gio.h"
#include "gmemorymonitorbase.h"
#include "gmemorymonitorpoll.h"
typedef struct
{
double simulated_mem_free_ratio;
int expected_warning_level;
} TestData;
static void
memory_monitor_signal_cb (GMemoryMonitor *m,
GMemoryMonitorWarningLevel level,
void *user_data)
{
int *out_warning_level = user_data;
g_assert_cmpint (*out_warning_level, ==, -1);
*out_warning_level = level;
g_main_context_wakeup (NULL);
}
static void
test_dup_default (void)
{
GMemoryMonitor *monitor;
monitor = g_memory_monitor_dup_default ();
g_assert_nonnull (monitor);
g_object_unref (monitor);
}
static void
test_event (const void *data)
{
const TestData *test_data = data;
GMemoryMonitorPoll *monitor;
GError *local_error = NULL;
int warning_level = -1;
unsigned long warning_id;
monitor = g_object_new (G_TYPE_MEMORY_MONITOR_POLL,
"poll-interval-ms", 50,
"mem-free-ratio", test_data->simulated_mem_free_ratio,
NULL);
g_initable_init (G_INITABLE (monitor), NULL, &local_error);
g_assert_no_error (local_error);
warning_id = g_signal_connect (monitor, "low-memory-warning",
G_CALLBACK (memory_monitor_signal_cb), &warning_level);
while (warning_level == -1)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (warning_level, ==, test_data->expected_warning_level);
g_signal_handler_disconnect (monitor, warning_id);
g_object_unref (monitor);
}
static void
warning_cb (GMemoryMonitor *m,
GMemoryMonitorWarningLevel level,
GMainLoop *test_loop)
{
char *str = g_enum_to_string (G_TYPE_MEMORY_MONITOR_WARNING_LEVEL, level);
g_free (str);
g_main_loop_quit (test_loop);
}
static void
do_watch_memory (void)
{
GMemoryMonitor *m;
GMainLoop *loop;
m = g_memory_monitor_dup_default ();
loop = g_main_loop_new (NULL, TRUE);
g_signal_connect (G_OBJECT (m), "low-memory-warning",
G_CALLBACK (warning_cb), loop);
g_main_loop_run (loop);
g_signal_handlers_disconnect_by_func (G_OBJECT (m),
G_CALLBACK (warning_cb), loop);
g_main_loop_unref (loop);
g_clear_object (&m);
}
int
main (int argc, char **argv)
{
if (argc == 2 && strcmp (argv[1], "--watch") == 0)
{
do_watch_memory ();
return 0;
}
g_setenv ("GIO_USE_MEMORY_MONITOR", "poll", TRUE);
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/memory-monitor-poll/dup-default", test_dup_default);
g_test_add_data_func ("/memory-monitor-poll/critical-event",
&((const TestData) {
.simulated_mem_free_ratio = 0.19,
.expected_warning_level = 255,
}), test_event);
g_test_add_data_func ("/memory-monitor-poll/medium-event",
&((const TestData) {
.simulated_mem_free_ratio = 0.29,
.expected_warning_level = 100,
}), test_event);
g_test_add_data_func ("/memory-monitor-poll/low-event",
&((const TestData) {
.simulated_mem_free_ratio = 0.39,
.expected_warning_level = 50,
}), test_event);
return g_test_run ();
}

View File

@@ -0,0 +1,120 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2025 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
#include <glib/glib.h>
#include <glib/gstdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "gio.h"
#include "gmemorymonitorpsi.h"
typedef struct
{
char *mock_psi_path;
char *mock_proc_path;
} SetupData;
static void
tests_setup (SetupData *setup_data,
gconstpointer data)
{
char *proc_contents = NULL;
GError *local_error = NULL;
setup_data->mock_psi_path = g_build_filename (g_get_tmp_dir (), "cgroup", NULL);
g_assert_no_errno (mkfifo (setup_data->mock_psi_path, S_IRUSR | S_IWUSR));
setup_data->mock_proc_path = g_build_filename (g_get_tmp_dir (), "psi-proc", NULL);
proc_contents = g_strdup_printf ("0::%s", setup_data->mock_psi_path);
g_file_set_contents_full (setup_data->mock_proc_path, proc_contents,
-1, G_FILE_SET_CONTENTS_NONE,
S_IRUSR | S_IWUSR, &local_error);
g_assert_no_error (local_error);
g_free (proc_contents);
}
static void
tests_teardown (SetupData *setup_data,
gconstpointer data)
{
g_unlink (setup_data->mock_proc_path);
g_free (setup_data->mock_proc_path);
g_unlink (setup_data->mock_psi_path);
g_free (setup_data->mock_psi_path);
}
static void
memory_monitor_psi_signal_cb (GMemoryMonitor *m,
GMemoryMonitorWarningLevel level,
gpointer user_data)
{
int *out_warning_level = user_data;
*out_warning_level = level;
g_main_context_wakeup (NULL);
}
static void
test_receive_signals (SetupData *setup, gconstpointer data)
{
GMemoryMonitorPsi *monitor;
unsigned long warning_id;
int warning_level = -1;
GError *local_error = NULL;
monitor = g_object_new (G_TYPE_MEMORY_MONITOR_PSI,
"proc-path", setup->mock_proc_path,
NULL);
g_initable_init (G_INITABLE (monitor), NULL, &local_error);
g_assert_no_error (local_error);
g_file_set_contents (setup->mock_psi_path,
"test",
-1,
&local_error);
g_assert_no_error (local_error);
warning_id = g_signal_connect (monitor, "low-memory-warning",
G_CALLBACK (memory_monitor_psi_signal_cb), &warning_level);
while (warning_level == -1)
g_main_context_iteration (NULL, TRUE);
g_assert_true (warning_level == 50 ||
warning_level == 100 ||
warning_level == 255);
g_signal_handler_disconnect (monitor, warning_id);
g_object_unref (monitor);
}
int
main (int argc, char **argv)
{
g_setenv ("GIO_USE_MEMORY_MONITOR", "psi", TRUE);
g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
g_test_add ("/memory-monitor-psi/receive-signal", SetupData, NULL,
tests_setup, test_receive_signals, tests_teardown);
return g_test_run ();
}

View File

@@ -155,6 +155,18 @@ gio_tests = {
'win32-appinfo' : {},
}
if host_system == 'linux'
gio_tests += {
'memory-monitor-psi' : {},
}
endif
if glib_conf.has('HAVE_SYSINFO')
gio_tests += {
'memory-monitor-poll' : {},
}
endif
if glib_have_cocoa
gio_tests += {
'osx-appinfo' : {}

View File

@@ -759,6 +759,7 @@ functions = [
'strtoll_l',
'strtoull_l',
'symlink',
'sysinfo',
'timegm',
'unsetenv',
'uselocale',