mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-12-05 16:14:49 +01:00
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:
@@ -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
153
gio/gmemorymonitorbase.c
Normal 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
53
gio/gmemorymonitorbase.h
Normal 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
273
gio/gmemorymonitorpoll.c
Normal 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
38
gio/gmemorymonitorpoll.h
Normal 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
512
gio/gmemorymonitorpsi.c
Normal 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 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:
|
||||
* <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
38
gio/gmemorymonitorpsi.h
Normal 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__ */
|
||||
@@ -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',
|
||||
|
||||
144
gio/tests/memory-monitor-poll.c
Normal file
144
gio/tests/memory-monitor-poll.c
Normal 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 ();
|
||||
}
|
||||
120
gio/tests/memory-monitor-psi.c
Normal file
120
gio/tests/memory-monitor-psi.c
Normal 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 ();
|
||||
}
|
||||
@@ -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' : {}
|
||||
|
||||
@@ -759,6 +759,7 @@ functions = [
|
||||
'strtoll_l',
|
||||
'strtoull_l',
|
||||
'symlink',
|
||||
'sysinfo',
|
||||
'timegm',
|
||||
'unsetenv',
|
||||
'uselocale',
|
||||
|
||||
Reference in New Issue
Block a user