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 : :
21 : : #include "config.h"
22 : :
23 : : #include "gcancellable.h"
24 : : #include "gdbusnamewatching.h"
25 : : #include "gdbusproxy.h"
26 : : #include "ginitable.h"
27 : : #include "gioerror.h"
28 : : #include "giomodule-priv.h"
29 : : #include "glibintl.h"
30 : : #include "glib/glib-private.h"
31 : : #include "glib/gstdio.h"
32 : : #include "gmemorymonitor.h"
33 : : #include "gmemorymonitorbase.h"
34 : : #include "gmemorymonitorpsi.h"
35 : :
36 : : #include <errno.h>
37 : : #include <fcntl.h>
38 : : #include <unistd.h>
39 : :
40 : : /**
41 : : * GMemoryMonitorPsi:
42 : : *
43 : : * A Linux [iface@Gio.MemoryMonitor] which uses the kernel
44 : : * [pressure stall information](https://www.kernel.org/doc/html/latest/accounting/psi.html) (PSI).
45 : : *
46 : : * When it receives a PSI event, it emits
47 : : * [signal@Gio.MemoryMonitor::low-memory-warning] with an appropriate warning
48 : : * level.
49 : : *
50 : : * Since: 2.86
51 : : */
52 : :
53 : : /* Unprivileged users can also create monitors, with
54 : : * the only limitation that the window size must be a
55 : : * `multiple of 2s`, in order to prevent excessive resource usage.
56 : : * see: https://www.kernel.org/doc/html/latest/accounting/psi.html*/
57 : : #define PSI_WINDOW_SEC 2
58 : :
59 : : typedef enum {
60 : : PROP_PROC_PATH = 1,
61 : : } GMemoryMonitorPsiProperty;
62 : :
63 : : typedef enum
64 : : {
65 : : MEMORY_PRESSURE_MONITOR_TRIGGER_SOME,
66 : : MEMORY_PRESSURE_MONITOR_TRIGGER_FULL,
67 : : MEMORY_PRESSURE_MONITOR_TRIGGER_MFD
68 : : } MemoryPressureMonitorTriggerType;
69 : :
70 : : /* Each trigger here results in an open fd for the lifetime
71 : : * of the `GMemoryMonitor`, so don’t add too many */
72 : : static const struct
73 : : {
74 : : MemoryPressureMonitorTriggerType trigger_type;
75 : : int threshold_ms;
76 : : } triggers[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT] = {
77 : : { MEMORY_PRESSURE_MONITOR_TRIGGER_SOME, 70 }, /* 70ms out of 2sec for partial stall */
78 : : { MEMORY_PRESSURE_MONITOR_TRIGGER_SOME, 100 }, /* 100ms out of 2sec for partial stall */
79 : : { MEMORY_PRESSURE_MONITOR_TRIGGER_FULL, 100 }, /* 100ms out of 2sec for complete stall */
80 : : };
81 : :
82 : : typedef struct
83 : : {
84 : : GSource source;
85 : : GPollFD *pollfd;
86 : : GMemoryMonitorLowMemoryLevel level_type;
87 : : GWeakRef monitor_weak;
88 : : } MemoryMonitorSource;
89 : :
90 : : typedef gboolean (*MemoryMonitorCallbackFunc) (GMemoryMonitorPsi *monitor,
91 : : GMemoryMonitorLowMemoryLevel level_type,
92 : : void *user_data);
93 : :
94 : : #define G_MEMORY_MONITOR_PSI_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable))
95 : :
96 : : static void g_memory_monitor_psi_iface_init (GMemoryMonitorInterface *iface);
97 : : static void g_memory_monitor_psi_initable_iface_init (GInitableIface *iface);
98 : :
99 : : struct _GMemoryMonitorPsi
100 : : {
101 : : GMemoryMonitorBase parent_instance;
102 : : GMainContext *worker; /* (unowned) */
103 : : GSource *triggers[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT]; /* (owned) (nullable) */
104 : :
105 : : char *cg_path;
106 : : char *proc_path;
107 : :
108 : : gboolean proc_override;
109 : : };
110 : :
111 : 284 : G_DEFINE_TYPE_WITH_CODE (GMemoryMonitorPsi, g_memory_monitor_psi, G_TYPE_MEMORY_MONITOR_BASE,
112 : : G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
113 : : g_memory_monitor_psi_initable_iface_init)
114 : : G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR,
115 : : g_memory_monitor_psi_iface_init)
116 : : _g_io_modules_ensure_extension_points_registered ();
117 : : g_io_extension_point_implement (G_MEMORY_MONITOR_EXTENSION_POINT_NAME,
118 : : g_define_type_id,
119 : : "psi",
120 : : 20))
121 : :
122 : : static void
123 : 1 : g_memory_monitor_psi_init (GMemoryMonitorPsi *psi)
124 : : {
125 : 1 : }
126 : :
127 : : static void
128 : 1 : g_memory_monitor_psi_set_property (GObject *object,
129 : : guint prop_id,
130 : : const GValue *value,
131 : : GParamSpec *pspec)
132 : : {
133 : 1 : GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
134 : :
135 : 1 : switch ((GMemoryMonitorPsiProperty) prop_id)
136 : : {
137 : 1 : case PROP_PROC_PATH:
138 : : /* Construct only */
139 : 1 : g_assert (monitor->proc_path == NULL);
140 : 1 : monitor->proc_path = g_value_dup_string (value);
141 : 1 : if (monitor->proc_path != NULL)
142 : 1 : monitor->proc_override = TRUE;
143 : 1 : break;
144 : 0 : default:
145 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
146 : 0 : break;
147 : : }
148 : 1 : }
149 : :
150 : : static void
151 : 0 : g_memory_monitor_psi_get_property (GObject *object,
152 : : guint prop_id,
153 : : GValue *value,
154 : : GParamSpec *pspec)
155 : : {
156 : 0 : GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
157 : :
158 : 0 : switch ((GMemoryMonitorPsiProperty) prop_id)
159 : : {
160 : 0 : case PROP_PROC_PATH:
161 : 0 : g_value_set_string (value, monitor->proc_override ? monitor->proc_path : NULL);
162 : 0 : break;
163 : 0 : default:
164 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
165 : 0 : break;
166 : : }
167 : 0 : }
168 : :
169 : : static gboolean
170 : 3 : g_memory_monitor_low_trigger_cb (GMemoryMonitorPsi *monitor,
171 : : GMemoryMonitorLowMemoryLevel level_type,
172 : : void *user_data)
173 : : {
174 : : gdouble mem_ratio;
175 : :
176 : : /* Should be executed in the worker context */
177 : 3 : g_assert (g_main_context_is_owner (monitor->worker));
178 : :
179 : 3 : mem_ratio = g_memory_monitor_base_query_mem_ratio ();
180 : :
181 : : /* if the test is running, skip memory ratio test */
182 : 3 : if (!monitor->proc_override)
183 : : {
184 : : /* if mem free ratio > 0.5, don't signal */
185 : 0 : if (mem_ratio < 0.0)
186 : 0 : return G_SOURCE_REMOVE;
187 : 0 : if (mem_ratio > 0.5)
188 : 0 : return G_SOURCE_CONTINUE;
189 : : }
190 : :
191 : 3 : g_memory_monitor_base_send_event_to_user (G_MEMORY_MONITOR_BASE (monitor), level_type);
192 : :
193 : 3 : return G_SOURCE_CONTINUE;
194 : : }
195 : :
196 : : static gboolean
197 : 3 : event_check (GSource *source)
198 : : {
199 : 3 : MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
200 : :
201 : 3 : if (ev_source->pollfd->revents)
202 : 3 : return G_SOURCE_CONTINUE;
203 : :
204 : 0 : return G_SOURCE_REMOVE;
205 : : }
206 : :
207 : : static gboolean
208 : 3 : event_dispatch (GSource *source,
209 : : GSourceFunc callback,
210 : : gpointer user_data)
211 : : {
212 : 3 : MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
213 : 3 : GMemoryMonitorPsi *monitor = NULL;
214 : :
215 : 3 : monitor = g_weak_ref_get (&ev_source->monitor_weak);
216 : 3 : if (monitor == NULL)
217 : 0 : return G_SOURCE_REMOVE;
218 : :
219 : 3 : if (monitor->proc_override)
220 : : {
221 : 3 : if (!(g_source_query_unix_fd (source, ev_source->pollfd) & G_IO_IN))
222 : : {
223 : 0 : g_object_unref (monitor);
224 : 0 : return G_SOURCE_CONTINUE;
225 : : }
226 : : }
227 : : else
228 : : {
229 : 0 : if (!(g_source_query_unix_fd (source, ev_source->pollfd) & G_IO_PRI))
230 : : {
231 : 0 : g_object_unref (monitor);
232 : 0 : return G_SOURCE_CONTINUE;
233 : : }
234 : : }
235 : :
236 : 3 : if (callback)
237 : 3 : ((MemoryMonitorCallbackFunc) callback) (monitor, ev_source->level_type, user_data);
238 : :
239 : 3 : g_object_unref (monitor);
240 : :
241 : 3 : return G_SOURCE_CONTINUE;
242 : : }
243 : :
244 : : static void
245 : 3 : event_finalize (GSource *source)
246 : : {
247 : 3 : MemoryMonitorSource *ev_source = (MemoryMonitorSource *) source;
248 : :
249 : 3 : g_weak_ref_clear (&ev_source->monitor_weak);
250 : 3 : }
251 : :
252 : : static GSourceFuncs memory_monitor_event_funcs = {
253 : : .check = event_check,
254 : : .dispatch = event_dispatch,
255 : : .finalize = event_finalize,
256 : : };
257 : :
258 : : static GSource *
259 : 3 : g_memory_monitor_create_source (GMemoryMonitorPsi *monitor,
260 : : int fd,
261 : : GMemoryMonitorLowMemoryLevel level_type,
262 : : gboolean is_path_override)
263 : : {
264 : : MemoryMonitorSource *source;
265 : :
266 : 3 : source = (MemoryMonitorSource *) g_source_new (&memory_monitor_event_funcs, sizeof (MemoryMonitorSource));
267 : 3 : if (is_path_override)
268 : 3 : source->pollfd = g_source_add_unix_fd ((GSource *) source, fd, G_IO_IN | G_IO_ERR);
269 : : else
270 : 0 : source->pollfd = g_source_add_unix_fd ((GSource *) source, fd, G_IO_PRI | G_IO_ERR);
271 : 3 : source->level_type = level_type;
272 : 3 : g_weak_ref_init (&source->monitor_weak, monitor);
273 : :
274 : 3 : return (GSource *) source;
275 : : }
276 : :
277 : : static gboolean
278 : 1 : g_memory_monitor_psi_calculate_mem_pressure_path (GMemoryMonitorPsi *monitor,
279 : : GError **error)
280 : : {
281 : : pid_t pid;
282 : 1 : gchar *path_read = NULL;
283 : 1 : gchar *replacement = NULL;
284 : :
285 : 1 : if (!monitor->proc_override)
286 : : {
287 : 0 : pid = getpid ();
288 : 0 : monitor->proc_path = g_strdup_printf ("/proc/%d/cgroup", pid);
289 : : }
290 : :
291 : 1 : if (!g_file_get_contents (monitor->proc_path, &path_read, NULL, error))
292 : : {
293 : 0 : g_free (path_read);
294 : 0 : return FALSE;
295 : : }
296 : :
297 : : /* cgroupv2 is only supported and the format is shown as follows:
298 : : * ex: 0::/user.slice/user-0.slice/session-c3.scope */
299 : 1 : if (!g_str_has_prefix (path_read, "0::"))
300 : : {
301 : 0 : g_debug ("Unsupported cgroup path information.");
302 : 0 : g_free (path_read);
303 : 0 : return FALSE;
304 : : }
305 : :
306 : : /* drop "0::" */
307 : 1 : replacement = g_strdup (path_read + strlen ("0::"));
308 : 1 : g_free (path_read);
309 : 1 : if (replacement == NULL)
310 : : {
311 : 0 : g_debug ("Unsupported cgroup path format.");
312 : 0 : return FALSE;
313 : : }
314 : :
315 : 1 : replacement = g_strstrip (replacement);
316 : :
317 : 1 : if (monitor->proc_override)
318 : : {
319 : 1 : monitor->cg_path = g_steal_pointer (&replacement);
320 : 1 : return TRUE;
321 : : }
322 : :
323 : 0 : monitor->cg_path = g_build_filename ("/sys/fs/cgroup", replacement, "memory.pressure", NULL);
324 : 0 : g_debug ("cgroup path is %s", monitor->cg_path);
325 : :
326 : 0 : g_free (replacement);
327 : 0 : return g_file_test (monitor->cg_path, G_FILE_TEST_EXISTS);
328 : : }
329 : :
330 : : static GSource *
331 : 3 : g_memory_monitor_psi_setup_trigger (GMemoryMonitorPsi *monitor,
332 : : GMemoryMonitorLowMemoryLevel level_type,
333 : : int threshold_us,
334 : : int window_us,
335 : : GError **error)
336 : : {
337 : : GSource *source;
338 : : int fd;
339 : : int ret;
340 : : size_t wlen;
341 : 3 : gchar *trigger = NULL;
342 : :
343 : 3 : fd = g_open (monitor->cg_path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
344 : 3 : if (fd < 0)
345 : : {
346 : 0 : int errsv = errno;
347 : 0 : g_debug ("Error on opening %s: %s", monitor->cg_path, g_strerror (errsv));
348 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
349 : : "Error on opening ‘%s’: %s", monitor->cg_path, g_strerror (errsv));
350 : 0 : return NULL;
351 : : }
352 : :
353 : : /* The kernel PSI [1] trigger format is:
354 : : * <some|full> <stall amount in us> <time window in us>
355 : : * The “some” indicates the share of time in which at least some tasks are stalled on a given resource.
356 : : * The “full” indicates the share of time in which all non-idle tasks are stalled on a given resource simultaneously.
357 : : * "stall amount in us": The total stall time in us.
358 : : * "time window in us": The specific time window in us.
359 : : * e.g. writing "some 150000 1000000" would add 150ms threshold for partial memory stall measured within 1sec time window
360 : : *
361 : : * [1] https://docs.kernel.org/accounting/psi.html
362 : : */
363 : 3 : trigger = g_strdup_printf ("%s %d %d",
364 : 3 : (triggers[level_type].trigger_type == MEMORY_PRESSURE_MONITOR_TRIGGER_SOME) ? "some" : "full",
365 : : threshold_us,
366 : : window_us);
367 : :
368 : 3 : errno = 0;
369 : 3 : wlen = strlen (trigger) + 1;
370 : 6 : while (wlen > 0)
371 : : {
372 : : int errsv;
373 : 3 : g_debug ("Write trigger %s", trigger);
374 : 3 : ret = write (fd, trigger, wlen);
375 : 3 : errsv = errno;
376 : 3 : if (ret < 0)
377 : : {
378 : 0 : if (errsv == EINTR)
379 : : {
380 : : /* interrupted by signal, retry */
381 : 0 : continue;
382 : : }
383 : : else
384 : : {
385 : 0 : g_set_error (error,
386 : : G_IO_ERROR, G_IO_ERROR_FAILED,
387 : : "Error on setting PSI configurations: %s",
388 : : g_strerror (errsv));
389 : 0 : g_free (trigger);
390 : 0 : close (fd);
391 : 0 : return NULL;
392 : : }
393 : : }
394 : 3 : wlen -= ret;
395 : : }
396 : 3 : g_free (trigger);
397 : :
398 : 3 : source = g_memory_monitor_create_source (monitor, fd, level_type, monitor->proc_override);
399 : 3 : g_source_set_callback (source, G_SOURCE_FUNC (g_memory_monitor_low_trigger_cb), NULL, NULL);
400 : :
401 : 3 : return g_steal_pointer (&source);
402 : : }
403 : :
404 : : static gboolean
405 : 1 : g_memory_monitor_setup_psi (GMemoryMonitorPsi *monitor,
406 : : GError **error)
407 : : {
408 : 1 : if (!g_memory_monitor_psi_calculate_mem_pressure_path (monitor, error))
409 : 0 : return FALSE;
410 : :
411 : 4 : for (size_t i = 0; i < G_N_ELEMENTS (triggers); i++)
412 : : {
413 : : /* the user defined PSI is estimated per second and the unit is in micro second(us). */
414 : 6 : monitor->triggers[i] = g_memory_monitor_psi_setup_trigger (monitor,
415 : : i,
416 : 3 : triggers[i].threshold_ms * 1000,
417 : : PSI_WINDOW_SEC * 1000 * 1000,
418 : : error);
419 : 3 : if (monitor->triggers[i] == NULL)
420 : 0 : return FALSE;
421 : : }
422 : :
423 : 1 : return TRUE;
424 : : }
425 : :
426 : : static gboolean
427 : 1 : g_memory_monitor_psi_initable_init (GInitable *initable,
428 : : GCancellable *cancellable,
429 : : GError **error)
430 : : {
431 : 1 : GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (initable);
432 : :
433 : 1 : monitor->worker = GLIB_PRIVATE_CALL (g_get_worker_context) ();
434 : :
435 : 1 : if (g_memory_monitor_setup_psi (monitor, error))
436 : : {
437 : 4 : for (size_t i = 0; i < G_N_ELEMENTS (monitor->triggers); i++)
438 : : {
439 : 3 : g_source_attach (monitor->triggers[i], monitor->worker);
440 : : }
441 : : }
442 : : else
443 : : {
444 : 0 : g_debug ("PSI is not supported.");
445 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "PSI is not supported.");
446 : 0 : return FALSE;
447 : : }
448 : :
449 : 1 : return TRUE;
450 : : }
451 : :
452 : : static void
453 : 1 : g_memory_monitor_psi_finalize (GObject *object)
454 : : {
455 : 1 : GMemoryMonitorPsi *monitor = G_MEMORY_MONITOR_PSI (object);
456 : :
457 : 1 : g_free (monitor->cg_path);
458 : 1 : g_free (monitor->proc_path);
459 : :
460 : 4 : for (size_t i = 0; i < G_N_ELEMENTS (monitor->triggers); i++)
461 : : {
462 : 3 : g_source_destroy (monitor->triggers[i]);
463 : 3 : g_source_unref (monitor->triggers[i]);
464 : : }
465 : :
466 : 1 : G_OBJECT_CLASS (g_memory_monitor_psi_parent_class)->finalize (object);
467 : 1 : }
468 : :
469 : : static void
470 : 1 : g_memory_monitor_psi_class_init (GMemoryMonitorPsiClass *klass)
471 : : {
472 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
473 : :
474 : 1 : object_class->set_property = g_memory_monitor_psi_set_property;
475 : 1 : object_class->get_property = g_memory_monitor_psi_get_property;
476 : 1 : object_class->finalize = g_memory_monitor_psi_finalize;
477 : :
478 : : /**
479 : : * GMemoryMonitorPsi:proc-path: (nullable)
480 : : *
481 : : * Kernel PSI path to use, if not the default.
482 : : *
483 : : * This is typically only used for test purposes.
484 : : *
485 : : * Since: 2.86
486 : : */
487 : 1 : g_object_class_install_property (object_class,
488 : : PROP_PROC_PATH,
489 : : g_param_spec_string ("proc-path", NULL, NULL,
490 : : NULL,
491 : : G_PARAM_READWRITE |
492 : : G_PARAM_CONSTRUCT_ONLY |
493 : : G_PARAM_STATIC_STRINGS));
494 : 1 : }
495 : :
496 : : static void
497 : 1 : g_memory_monitor_psi_iface_init (GMemoryMonitorInterface *monitor_iface)
498 : : {
499 : 1 : }
500 : :
501 : : static void
502 : 1 : g_memory_monitor_psi_initable_iface_init (GInitableIface *iface)
503 : : {
504 : 1 : iface->init = g_memory_monitor_psi_initable_init;
505 : 1 : }
|