Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright 2025 Red Hat, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General
18 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 : : */
20 : : #include "config.h"
21 : :
22 : : #include "gcancellable.h"
23 : : #include "gdbusnamewatching.h"
24 : : #include "gdbusproxy.h"
25 : : #include "ginitable.h"
26 : : #include "gioerror.h"
27 : : #include "giomodule-priv.h"
28 : : #include "glib/gstdio.h"
29 : : #include "glib/glib-private.h"
30 : : #include "glibintl.h"
31 : : #include "gmemorymonitor.h"
32 : : #include "gmemorymonitorpoll.h"
33 : :
34 : : #include <fcntl.h>
35 : : #include <unistd.h>
36 : :
37 : : /**
38 : : * GMemoryMonitorPoll:
39 : : *
40 : : * A [iface@Gio.MemoryMonitor] which polls the system free/used
41 : : * memory ratio on a fixed timer.
42 : : *
43 : : * It polls, for example, every 10 seconds, and emits different
44 : : * [signal@Gio.MemoryMonitor::low-memory-warning] signals if it falls below several
45 : : * ‘low’ thresholds.
46 : : *
47 : : * The system free/used memory ratio is queried using [`sysinfo()`](man:sysinfo(2)).
48 : : *
49 : : * This is intended as a fallback implementation of [iface@Gio.MemoryMonitor] in case
50 : : * other, more performant, implementations are not supported on the system.
51 : : *
52 : : * Since: 2.86
53 : : */
54 : :
55 : : typedef enum {
56 : : PROP_MEM_FREE_RATIO = 1,
57 : : PROP_POLL_INTERVAL_MS,
58 : : } GMemoryMonitorPollProperty;
59 : :
60 : : #define G_MEMORY_MONITOR_POLL_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
61 : :
62 : : static void g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *iface);
63 : : static void g_memory_monitor_poll_initable_iface_init (GInitableIface *iface);
64 : :
65 : : struct _GMemoryMonitorPoll
66 : : {
67 : : GMemoryMonitorBase parent_instance;
68 : :
69 : : GMainContext *worker; /* (unowned) */
70 : : GSource *source_timeout; /* (owned) */
71 : :
72 : : /* it overrides the default timeout when running the test */
73 : : unsigned int poll_interval_ms; /* zero to use the default */
74 : : gdouble mem_free_ratio;
75 : : };
76 : :
77 : : /* the default monitor timeout */
78 : : #define G_MEMORY_MONITOR_PSI_DEFAULT_SEC 10
79 : :
80 : 297 : G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPoll, g_memory_monitor_poll, G_TYPE_MEMORY_MONITOR_BASE,
81 : : G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
82 : : g_memory_monitor_poll_initable_iface_init)
83 : : G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
84 : : g_memory_monitor_poll_iface_init)
85 : : _g_io_modules_ensure_extension_points_registered ();
86 : : g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
87 : : g_define_type_id,
88 : : "poll",
89 : : 10))
90 : :
91 : : static void
92 : 4 : g_memory_monitor_poll_init (GMemoryMonitorPoll *mem_poll)
93 : : {
94 : 4 : mem_poll->mem_free_ratio = -1.0;
95 : 4 : }
96 : :
97 : : static void
98 : 8 : g_memory_monitor_poll_set_property (GObject *object,
99 : : guint prop_id,
100 : : const GValue *value,
101 : : GParamSpec *pspec)
102 : : {
103 : 8 : GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
104 : :
105 : 8 : switch ((GMemoryMonitorPollProperty) prop_id)
106 : : {
107 : 4 : case PROP_MEM_FREE_RATIO:
108 : 4 : monitor->mem_free_ratio = g_value_get_double (value);
109 : 4 : break;
110 : 4 : case PROP_POLL_INTERVAL_MS:
111 : 4 : monitor->poll_interval_ms = g_value_get_uint (value);
112 : 4 : break;
113 : 0 : default:
114 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
115 : 0 : break;
116 : : }
117 : 8 : }
118 : :
119 : : static void
120 : 0 : g_memory_monitor_poll_get_property (GObject *object,
121 : : guint prop_id,
122 : : GValue *value,
123 : : GParamSpec *pspec)
124 : : {
125 : 0 : GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
126 : :
127 : 0 : switch ((GMemoryMonitorPollProperty) prop_id)
128 : : {
129 : 0 : case PROP_MEM_FREE_RATIO:
130 : 0 : g_value_set_double (value, monitor->mem_free_ratio);
131 : 0 : break;
132 : 0 : case PROP_POLL_INTERVAL_MS:
133 : 0 : g_value_set_uint (value, monitor->poll_interval_ms);
134 : 0 : break;
135 : 0 : default:
136 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
137 : 0 : break;
138 : : }
139 : 0 : }
140 : :
141 : : typedef struct
142 : : {
143 : : GWeakRef monitor_weak; /* (element-type GMemoryMonitorPoll) */
144 : : } CallbackData;
145 : :
146 : : static CallbackData *
147 : 4 : callback_data_new (GMemoryMonitorPoll *monitor)
148 : : {
149 : 4 : CallbackData *data = g_new0 (CallbackData, 1);
150 : 4 : g_weak_ref_init (&data->monitor_weak, monitor);
151 : 4 : return g_steal_pointer (&data);
152 : : }
153 : :
154 : : static void
155 : 3 : callback_data_free (CallbackData *data)
156 : : {
157 : 3 : g_weak_ref_clear (&data->monitor_weak);
158 : 3 : g_free (data);
159 : 3 : }
160 : :
161 : : static gboolean
162 : 3 : g_memory_monitor_mem_ratio_cb (gpointer user_data)
163 : : {
164 : 3 : CallbackData *data = user_data;
165 : 3 : GMemoryMonitorPoll *monitor = NULL;
166 : : gdouble mem_ratio;
167 : 3 : GMemoryMonitorLowMemoryLevel warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID;
168 : :
169 : : /* It’s possible for the dispatch of this callback to race with finalising
170 : : * the `GMemoryMonitorPoll`, hence the use of a thread-safe weak ref. */
171 : 3 : monitor = g_weak_ref_get (&data->monitor_weak);
172 : 3 : if (monitor == NULL)
173 : 0 : return G_SOURCE_REMOVE;
174 : :
175 : : /* Should be executed in the worker context */
176 : 3 : g_assert (g_main_context_is_owner (monitor->worker));
177 : :
178 : 3 : mem_ratio = g_memory_monitor_base_query_mem_ratio ();
179 : :
180 : : /* free ratio override */
181 : 3 : if (monitor->mem_free_ratio >= 0.0)
182 : 3 : mem_ratio = monitor->mem_free_ratio;
183 : :
184 : 3 : if (mem_ratio < 0.0)
185 : : {
186 : 0 : g_clear_object (&monitor);
187 : 0 : return G_SOURCE_REMOVE;
188 : : }
189 : :
190 : 3 : if (mem_ratio > 0.5)
191 : : {
192 : 0 : g_clear_object (&monitor);
193 : 0 : return G_SOURCE_CONTINUE;
194 : : }
195 : :
196 : 3 : g_debug ("memory free ratio %f", mem_ratio);
197 : :
198 : 3 : if (mem_ratio < 0.2)
199 : 1 : warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL;
200 : 2 : else if (mem_ratio < 0.3)
201 : 1 : warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM;
202 : 1 : else if (mem_ratio < 0.4)
203 : 1 : warning_level = G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW;
204 : :
205 : 3 : if (warning_level != G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID)
206 : 3 : g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), warning_level);
207 : :
208 : 3 : g_clear_object (&monitor);
209 : :
210 : 3 : return G_SOURCE_CONTINUE;
211 : : }
212 : :
213 : : static gboolean
214 : 4 : g_memory_monitor_poll_initable_init (GInitable *initable,
215 : : GCancellable *cancellable,
216 : : GError **error)
217 : : {
218 : 4 : GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (initable);
219 : : GSource *source;
220 : :
221 : 4 : if (monitor->poll_interval_ms > 0)
222 : : {
223 : 3 : if (monitor->poll_interval_ms < G_TIME_SPAN_MILLISECOND)
224 : 3 : source = g_timeout_source_new (monitor->poll_interval_ms);
225 : : else
226 : 0 : source = g_timeout_source_new_seconds (monitor->poll_interval_ms / G_TIME_SPAN_MILLISECOND);
227 : : }
228 : : else
229 : : {
230 : : /* default 10 second */
231 : 1 : source = g_timeout_source_new_seconds (G_MEMORY_MONITOR_PSI_DEFAULT_SEC);
232 : : }
233 : :
234 : 4 : g_source_set_callback (source, g_memory_monitor_mem_ratio_cb,
235 : 4 : callback_data_new (monitor), (GDestroyNotify) callback_data_free);
236 : 4 : monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
237 : 4 : g_source_attach (source, monitor->worker);
238 : 4 : monitor->source_timeout = g_steal_pointer (&source);
239 : :
240 : 4 : return TRUE;
241 : : }
242 : :
243 : : static void
244 : 3 : g_memory_monitor_poll_finalize (GObject *object)
245 : : {
246 : 3 : GMemoryMonitorPoll *monitor = G_MEMORY_MONITOR_POLL (object);
247 : :
248 : 3 : g_source_destroy (monitor->source_timeout);
249 : 3 : g_source_unref (monitor->source_timeout);
250 : :
251 : 3 : G_OBJECT_CLASS (g_memory_monitor_poll_parent_class)->finalize (object);
252 : 3 : }
253 : :
254 : : static void
255 : 1 : g_memory_monitor_poll_class_init (GMemoryMonitorPollClass *klass)
256 : : {
257 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
258 : :
259 : 1 : object_class->set_property = g_memory_monitor_poll_set_property;
260 : 1 : object_class->get_property = g_memory_monitor_poll_get_property;
261 : 1 : object_class->finalize = g_memory_monitor_poll_finalize;
262 : :
263 : : /**
264 : : * GMemoryMonitorPoll:mem-free-ratio:
265 : : *
266 : : * Override the memory free ratio
267 : : *
268 : : * Since: 2.86
269 : : */
270 : 1 : g_object_class_install_property (object_class,
271 : : PROP_MEM_FREE_RATIO,
272 : : g_param_spec_double ("mem-free-ratio", NULL, NULL,
273 : : -1.0, 1.0,
274 : : -1.0,
275 : : G_PARAM_READWRITE |
276 : : G_PARAM_CONSTRUCT_ONLY |
277 : : G_PARAM_STATIC_STRINGS));
278 : :
279 : : /**
280 : : * GMemoryMonitorPoll:poll-interval-ms:
281 : : *
282 : : * Override the poll interval for monitoring the memory usage.
283 : : *
284 : : * The interval is in milliseconds. Zero means to use the default interval.
285 : : *
286 : : * Since: 2.86
287 : : */
288 : 1 : g_object_class_install_property (object_class,
289 : : PROP_POLL_INTERVAL_MS,
290 : : g_param_spec_uint ("poll-interval-ms", NULL, NULL,
291 : : 0, G_MAXUINT,
292 : : 0,
293 : : G_PARAM_READWRITE |
294 : : G_PARAM_CONSTRUCT_ONLY |
295 : : G_PARAM_STATIC_STRINGS));
296 : :
297 : 1 : }
298 : :
299 : : static void
300 : 1 : g_memory_monitor_poll_iface_init (GMemoryMonitorInterface *monitor_iface)
301 : : {
302 : 1 : }
303 : :
304 : : static void
305 : 1 : g_memory_monitor_poll_initable_iface_init (GInitableIface *iface)
306 : : {
307 : 1 : iface->init = g_memory_monitor_poll_initable_init;
308 : 1 : }
309 : :
|