/src/glib/gio/gmemorymonitorbase.c
Line | Count | Source |
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 "ginitable.h" |
25 | | #include "gioerror.h" |
26 | | #include "giomodule-priv.h" |
27 | | #include "glib/gstdio.h" |
28 | | #include "glibintl.h" |
29 | | #include "gmemorymonitor.h" |
30 | | #include "gmemorymonitorbase.h" |
31 | | |
32 | | #ifdef HAVE_SYSINFO |
33 | | #include <sys/sysinfo.h> |
34 | | #elif defined(HAVE_UNISTD_H) |
35 | | #include <unistd.h> |
36 | | #endif |
37 | | |
38 | | /** |
39 | | * GMemoryMonitorBase: |
40 | | * |
41 | | * An abstract base class for implementations of [iface@Gio.MemoryMonitor] which |
42 | | * provides several defined warning levels (`GLowMemoryLevel`) and tracks how |
43 | | * often they are notified to the user via [signal@Gio.MemoryMonitor::low-memory-warning] |
44 | | * to limit the number of signal emissions to one every 15 seconds for each level. |
45 | | * [method@Gio.MemoryMonitorBase.send_event_to_user] is provided for this purpose. |
46 | | */ |
47 | | |
48 | | /* The interval between sending a signal in second */ |
49 | 0 | #define RECOVERY_INTERVAL_SEC 15 |
50 | | |
51 | | #define G_MEMORY_MONITOR_BASE_GET_INITABLE_IFACE(o) (G_TYPE_INSTANCE_GET_INTERFACE ((o), G_TYPE_INITABLE, GInitable)) |
52 | | |
53 | | static void g_memory_monitor_base_iface_init (GMemoryMonitorInterface *iface); |
54 | | static void g_memory_monitor_base_initable_iface_init (GInitableIface *iface); |
55 | | |
56 | | typedef struct |
57 | | { |
58 | | GObject parent_instance; |
59 | | |
60 | | guint64 last_trigger_us[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT]; |
61 | | } GMemoryMonitorBasePrivate; |
62 | | |
63 | | |
64 | 0 | G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GMemoryMonitorBase, g_memory_monitor_base, G_TYPE_OBJECT, |
65 | 0 | G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, |
66 | 0 | g_memory_monitor_base_initable_iface_init) |
67 | 0 | G_IMPLEMENT_INTERFACE (G_TYPE_MEMORY_MONITOR, |
68 | 0 | g_memory_monitor_base_iface_init) |
69 | 0 | G_ADD_PRIVATE (GMemoryMonitorBase)) |
70 | 0 |
|
71 | 0 | gdouble |
72 | 0 | g_memory_monitor_base_query_mem_ratio (void) |
73 | 0 | { |
74 | 0 | #ifdef HAVE_SYSINFO |
75 | 0 | struct sysinfo info; |
76 | |
|
77 | 0 | if (sysinfo (&info)) |
78 | 0 | return -1.0; |
79 | | |
80 | 0 | if (info.totalram == 0) |
81 | 0 | return -1.0; |
82 | | |
83 | 0 | return (gdouble) ((gdouble) info.freeram / (gdouble) info.totalram); |
84 | | #elif defined(_SC_PHYS_PAGES) && defined(_SC_AVPHYS_PAGES) |
85 | | /* Implementation for Solaris, where sysinfo() does not return RAM usage information */ |
86 | | long totalram = sysconf (_SC_PHYS_PAGES); |
87 | | long freeram = sysconf (_SC_AVPHYS_PAGES); |
88 | | |
89 | | if (totalram <= 0 || freeram < 0) |
90 | | return -1.0; |
91 | | |
92 | | return (gdouble) ((gdouble) freeram / (gdouble) totalram); |
93 | | #else |
94 | | return -1.0; |
95 | | #endif |
96 | 0 | } |
97 | | |
98 | | GMemoryMonitorWarningLevel |
99 | | g_memory_monitor_base_level_enum_to_byte (GMemoryMonitorLowMemoryLevel level) |
100 | 0 | { |
101 | 0 | const GMemoryMonitorWarningLevel level_bytes[G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT] = { |
102 | 0 | [G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_LOW] = 50, |
103 | 0 | [G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_MEDIUM] = 100, |
104 | 0 | [G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_CRITICAL] = 255 |
105 | 0 | }; |
106 | |
|
107 | 0 | if ((int) level < G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID || |
108 | 0 | (int) level >= G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_COUNT) |
109 | 0 | g_assert_not_reached (); |
110 | | |
111 | 0 | if (level == G_MEMORY_MONITOR_LOW_MEMORY_LEVEL_INVALID) |
112 | 0 | return 0; |
113 | | |
114 | 0 | return level_bytes[level]; |
115 | 0 | } |
116 | | |
117 | | typedef struct |
118 | | { |
119 | | GWeakRef monitor_weak; |
120 | | GMemoryMonitorWarningLevel level; |
121 | | } SendEventData; |
122 | | |
123 | | static void |
124 | | send_event_data_free (SendEventData *data) |
125 | 0 | { |
126 | 0 | g_weak_ref_clear (&data->monitor_weak); |
127 | 0 | g_free (data); |
128 | 0 | } |
129 | | |
130 | | /* Invoked in the global default main context */ |
131 | | static gboolean |
132 | | send_event_cb (void *user_data) |
133 | 0 | { |
134 | 0 | SendEventData *data = user_data; |
135 | 0 | GMemoryMonitor *monitor = g_weak_ref_get (&data->monitor_weak); |
136 | |
|
137 | 0 | if (monitor != NULL) |
138 | 0 | g_signal_emit_by_name (monitor, "low-memory-warning", data->level); |
139 | |
|
140 | 0 | g_clear_object (&monitor); |
141 | |
|
142 | 0 | return G_SOURCE_REMOVE; |
143 | 0 | } |
144 | | |
145 | | void |
146 | | g_memory_monitor_base_send_event_to_user (GMemoryMonitorBase *monitor, |
147 | | GMemoryMonitorLowMemoryLevel warning_level) |
148 | 0 | { |
149 | 0 | gint64 current_time; |
150 | 0 | GMemoryMonitorBasePrivate *priv = g_memory_monitor_base_get_instance_private (monitor); |
151 | |
|
152 | 0 | current_time = g_get_monotonic_time (); |
153 | |
|
154 | 0 | if (priv->last_trigger_us[warning_level] == 0 || |
155 | 0 | (current_time - priv->last_trigger_us[warning_level]) > (RECOVERY_INTERVAL_SEC * G_USEC_PER_SEC)) |
156 | 0 | { |
157 | 0 | SendEventData *data = NULL; |
158 | |
|
159 | 0 | g_debug ("Send low memory signal with warning level %u", warning_level); |
160 | | |
161 | | /* The signal has to be emitted in the global default main context, |
162 | | * because the `GMemoryMonitor` is a singleton which may have been created |
163 | | * in an arbitrary thread, or which may be calling this function from the |
164 | | * GLib worker thread. */ |
165 | 0 | data = g_new0 (SendEventData, 1); |
166 | 0 | g_weak_ref_init (&data->monitor_weak, monitor); |
167 | 0 | data->level = g_memory_monitor_base_level_enum_to_byte (warning_level); |
168 | 0 | g_main_context_invoke_full (NULL, G_PRIORITY_DEFAULT, send_event_cb, |
169 | 0 | g_steal_pointer (&data), (GDestroyNotify) send_event_data_free); |
170 | 0 | priv->last_trigger_us[warning_level] = current_time; |
171 | 0 | } |
172 | 0 | } |
173 | | |
174 | | static gboolean |
175 | | g_memory_monitor_base_initable_init (GInitable *initable, |
176 | | GCancellable *cancellable, |
177 | | GError **error) |
178 | 0 | { |
179 | 0 | return TRUE; |
180 | 0 | } |
181 | | |
182 | | static void |
183 | | g_memory_monitor_base_init (GMemoryMonitorBase *monitor) |
184 | 0 | { |
185 | 0 | } |
186 | | |
187 | | static void |
188 | | g_memory_monitor_base_class_init (GMemoryMonitorBaseClass *klass) |
189 | 0 | { |
190 | 0 | } |
191 | | |
192 | | static void |
193 | | g_memory_monitor_base_iface_init (GMemoryMonitorInterface *monitor_iface) |
194 | 0 | { |
195 | 0 | } |
196 | | |
197 | | static void |
198 | | g_memory_monitor_base_initable_iface_init (GInitableIface *iface) |
199 | 0 | { |
200 | 0 | iface->init = g_memory_monitor_base_initable_init; |
201 | 0 | } |