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: + *