diff --git a/gio/giomodule.c b/gio/giomodule.c
index 76c202848..38761e4fd 100644
--- a/gio/giomodule.c
+++ b/gio/giomodule.c
@@ -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 ());
diff --git a/gio/gmemorymonitorbase.c b/gio/gmemorymonitorbase.c
new file mode 100644
index 000000000..ce28ebe33
--- /dev/null
+++ b/gio/gmemorymonitorbase.c
@@ -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 .
+ */
+
+#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
+#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;
+}
diff --git a/gio/gmemorymonitorbase.h b/gio/gmemorymonitorbase.h
new file mode 100644
index 000000000..847d55515
--- /dev/null
+++ b/gio/gmemorymonitorbase.h
@@ -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 .
+ */
+
+#ifndef __G_MEMORY_MONITOR_BASE_H__
+#define __G_MEMORY_MONITOR_BASE_H__
+
+#include
+
+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__ */
diff --git a/gio/gmemorymonitorpoll.c b/gio/gmemorymonitorpoll.c
new file mode 100644
index 000000000..d5d4e75f5
--- /dev/null
+++ b/gio/gmemorymonitorpoll.c
@@ -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 .
+ */
+#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
+#include
+
+/**
+ * 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;
+}
+
diff --git a/gio/gmemorymonitorpoll.h b/gio/gmemorymonitorpoll.h
new file mode 100644
index 000000000..6d086aa6d
--- /dev/null
+++ b/gio/gmemorymonitorpoll.h
@@ -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 .
+ */
+
+#ifndef __G_MEMORY_MONITOR_POLL_H__
+#define __G_MEMORY_MONITOR_POLL_H__
+
+#include "gmemorymonitorbase.h"
+
+#include
+
+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__ */
diff --git a/gio/gmemorymonitorpsi.c b/gio/gmemorymonitorpsi.c
new file mode 100644
index 000000000..b21f504ea
--- /dev/null
+++ b/gio/gmemorymonitorpsi.c
@@ -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 .
+ */
+
+#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
+#include
+#include
+
+/**
+ * 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 don’t 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:
+ *