1 /*============================================================================
2 * Base memory usage information (System and Library dependent)
3 *============================================================================*/
4
5 /*
6 This file is part of Code_Saturne, a general-purpose CFD tool.
7
8 Copyright (C) 1998-2021 EDF S.A.
9
10 This program is free software; you can redistribute it and/or modify it under
11 the terms of the GNU General Public License as published by the Free Software
12 Foundation; either version 2 of the License, or (at your option) any later
13 version.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 details.
19
20 You should have received a copy of the GNU General Public License along with
21 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin
22 Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25 /*----------------------------------------------------------------------------*/
26
27 #include "ecs_def.h"
28
29 /* OS type */
30
31 #if defined(__linux__) || defined(__linux) || defined(linux)
32 #define ECS_OS_Linux
33
34 #endif
35
36 /*
37 * Standard C library headers
38 */
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43
44 #if defined (ECS_OS_Linux) && defined(HAVE_SYS_STAT_H) \
45 && defined(HAVE_SYS_TYPES_H) && defined(HAVE_UNISTD_H) \
46
47 #include <sys/types.h>
48 #include <unistd.h>
49 #include <sys/stat.h>
50 #include <fcntl.h>
51
52 #elif defined(HAVE_GETRUSAGE)
53 #include <sys/time.h>
54 #include <sys/resource.h>
55 #include <unistd.h>
56 #endif
57
58 #if defined(HAVE_UNISTD_H) && defined(HAVE_SBRK)
59 #if defined (ECS_OS_Linux)
60 #define __USE_MISC 1
61 #endif
62 #include <unistd.h>
63 #endif
64
65 #if defined(HAVE_STDDEF_H)
66 #include <stddef.h>
67 #endif
68
69 /*
70 * Optional library and ECS headers
71 */
72
73 #include "ecs_mem_usage.h"
74
75 /*-----------------------------------------------------------------------------*/
76
77 BEGIN_C_DECLS
78
79 /*-------------------------------------------------------------------------------
80 * Local type definitions
81 *-----------------------------------------------------------------------------*/
82
83 /*-----------------------------------------------------------------------------
84 * Local static variable definitions
85 *-----------------------------------------------------------------------------*/
86
87 static int _ecs_mem_usage_global_initialized = 0;
88
89 static size_t _ecs_mem_usage_global_max_pr = 0;
90
91 #if defined(USE_SBRK)
92 static void *_ecs_mem_usage_global_init_sbrk = NULL;
93 #endif
94
95 #if defined (ECS_OS_Linux) && defined(HAVE_SYS_STAT_H) \
96 && defined(HAVE_SYS_TYPES_H)
97 static int _ecs_mem_usage_proc_file_init = 0;
98 #endif
99
100 /*-----------------------------------------------------------------------------
101 * Local function definitions
102 *-----------------------------------------------------------------------------*/
103
104 #if defined (ECS_OS_Linux) && defined(HAVE_SYS_STAT_H) \
105 && defined(HAVE_SYS_TYPES_H)
106
107 /*!
108 * \brief Initialize current process memory use count depending on system.
109 */
110
111 static void
_ecs_mem_usage_pr_size_init(void)112 _ecs_mem_usage_pr_size_init(void)
113 {
114 char buf[512]; /* should be large enough for "/proc/%lu/status"
115 then beginning of file content */
116 int fd;
117 size_t r_size, i;
118 _Bool status_has_peak = false;
119 const pid_t pid = getpid();
120
121 /*
122 Under Linux with procfs, one line of the pseudo-file "/proc/pid/status"
123 (where pid is the process number) is of the following form:
124 VmSize: xxxx kB
125 This line may be the 12th to 13th for a 2.6.x kernel.
126 On more recent 2.6.x kernels, another line (the 12th) is of the form:
127 VmPeak: xxxx kB
128 */
129
130 if (_ecs_mem_usage_proc_file_init != 0)
131 return;
132
133 sprintf(buf, "/proc/%lu/status", (unsigned long) pid);
134
135 fd = open(buf, O_RDONLY);
136
137 if (fd != -1) {
138
139 r_size = read(fd, buf, 512);
140
141 if (r_size > 32) { /* Leave a margin for "VmPeak" or "VmSize:" line */
142 r_size -= 32;
143 for (i = 0; i < r_size; i++) {
144 if (buf[i] == 'V' && strncmp(buf+i, "VmPeak:", 7) == 0) {
145 status_has_peak = true;
146 break;
147 }
148 }
149 for (i = 0; i < r_size; i++) {
150 if (buf[i] == 'V' && strncmp(buf+i, "VmSize:", 7) == 0)
151 break;
152 }
153 /* If VmSize was found, proc file may be used */
154 if (i < r_size) {
155 if (status_has_peak == true)
156 _ecs_mem_usage_proc_file_init = 1;
157 }
158 }
159
160 (void)close(fd);
161 }
162
163 /* If initialization failed for some reason (proc file unavailable or does
164 or does not contain the required fields), mark method as unusable */
165 if (_ecs_mem_usage_proc_file_init == 0)
166 _ecs_mem_usage_proc_file_init = -1;
167 }
168
169 /*!
170 * \brief Finalize current process memory use count depending on system.
171 */
172
173 static void
_ecs_mem_usage_pr_size_end(void)174 _ecs_mem_usage_pr_size_end(void)
175 {
176 if (_ecs_mem_usage_proc_file_init != 1)
177 return;
178 }
179
180 #else /* defined (ECS_OS_Linux) && ... */
181
182 #define _ecs_mem_usage_pr_size_init()
183 #define _ecs_mem_usage_pr_size_end()
184
185 #endif /* defined (ECS_OS_Linux) && ... */
186
187 /*============================================================================
188 * Public function definitions
189 *============================================================================*/
190
191 /*!
192 * \brief Initialize memory usage count depending on system.
193 *
194 * This functions checks if it has already been called, so
195 * it is safe to call more than once (though it is not
196 * thread-safe). Only the first call is effective.
197 */
198
199 void
ecs_mem_usage_init(void)200 ecs_mem_usage_init(void)
201 {
202 if (_ecs_mem_usage_global_initialized != 0)
203 return;
204
205 #if defined(USE_SBRK)
206
207 /*
208 We use sbrk() to know the size of the heap. This is not of any use
209 to guess at allocated memory when some part of the memory may
210 be allocated with mmap(), such as with glibc on Linux.
211 */
212
213 _ecs_mem_usage_global_init_sbrk = (void *) sbrk(0);
214
215 #endif /* (USE_SBRK) */
216
217 _ecs_mem_usage_global_initialized = 1;
218 }
219
220 /*!
221 * \brief End memory usage count depending on system.
222 */
223
224 void
ecs_mem_usage_end(void)225 ecs_mem_usage_end(void)
226 {
227 _ecs_mem_usage_pr_size_end();
228 }
229
230 /*!
231 * \brief Indicates if ecs_mem_usage_...() functions are initialized.
232 *
233 * \returns 1 if ecs_mem_usage_init has been called, 0 otherwise.
234 */
235
236 int
ecs_mem_usage_initialized(void)237 ecs_mem_usage_initialized(void)
238 {
239 return _ecs_mem_usage_global_initialized;
240 }
241
242 /*!
243 * \brief Return current process memory use (in kB) depending on system.
244 *
245 * If the information is not available (depending on availability of
246 * non-portable function calls), 0 is returned.
247 */
248
249 #if defined (ECS_OS_Linux) && defined(HAVE_SYS_STAT_H) \
250 && defined(HAVE_SYS_TYPES_H)
251
252 size_t
ecs_mem_usage_pr_size(void)253 ecs_mem_usage_pr_size(void)
254 {
255 size_t sys_mem_usage = 0;
256
257 /*
258 Under Linux with procfs, one line of the pseudo-file "/proc/pid/status"
259 (where pid is the process number) is of the following form:
260 VmSize: xxxx kB
261 With more recent kernels, we also have a line of the form:
262 VmPeak: xxxx kB
263 */
264
265 {
266 if (_ecs_mem_usage_proc_file_init == 0)
267 _ecs_mem_usage_pr_size_init();
268
269 if (_ecs_mem_usage_proc_file_init == 1) {
270
271 char buf[81]; /* should be large enough for "/proc/%lu/status" */
272 const pid_t pid = getpid();
273
274 FILE *fp;
275 unsigned long val;
276 char *s;
277
278 sprintf(buf, "/proc/%lu/status", (unsigned long) pid);
279 fp = fopen(buf, "r");
280
281 if (fp != NULL) {
282
283 int fields_read = 0;
284
285 while (fields_read < 2) {
286 s = fgets(buf, 80, fp);
287 if (s == NULL)
288 break;
289 if (strncmp(s, "VmSize:", 7) == 0) {
290 sscanf (s + 7, "%lu", &val);
291 sys_mem_usage = (size_t) val;
292 fields_read += 1;
293 }
294 else if (strncmp(s, "VmPeak:", 7) == 0) {
295 sscanf (s + 7, "%lu", &val);
296 if ((size_t) val > _ecs_mem_usage_global_max_pr)
297 _ecs_mem_usage_global_max_pr = (size_t) val;
298 fields_read += 1;
299 }
300 }
301
302 fclose(fp);
303
304 }
305 }
306
307 _ecs_mem_usage_pr_size_end();
308 }
309
310 if (sys_mem_usage > _ecs_mem_usage_global_max_pr)
311 _ecs_mem_usage_global_max_pr = sys_mem_usage;
312
313 return sys_mem_usage;
314 }
315
316 #elif defined(USE_SBRK)
317
318 size_t
ecs_mem_usage_pr_size(void)319 ecs_mem_usage_pr_size(void)
320 {
321 size_t alloc_size = 0;
322
323 if (_ecs_mem_usage_global_initialized) {
324 void *end_addr;
325
326 end_addr = (void *) sbrk(0);
327
328 #if defined(HAVE_PTRDIFF_T)
329 alloc_size = (size_t)( (ptrdiff_t)end_addr
330 - (ptrdiff_t)_ecs_mem_usage_global_init_sbrk) / 1024;
331 #else
332 alloc_size = (end_addr - _ecs_mem_usage_global_init_sbrk) / 1024;
333 #endif
334
335 }
336
337 if (alloc_size > _ecs_mem_usage_global_max_pr)
338 _ecs_mem_usage_global_max_pr = alloc_size;
339
340 return alloc_size;
341 }
342
343 #elif defined(HAVE_GETRUSAGE)
344
345 size_t
ecs_mem_usage_pr_size(void)346 ecs_mem_usage_pr_size(void)
347 {
348 size_t sys_mem_usage = 0;
349 struct rusage usage;
350
351 getrusage(RUSAGE_SELF, &usage);
352
353 sys_mem_usage = usage.ru_maxrss / 1024;
354
355 return sys_mem_usage;
356 }
357
358 #else /* Default case */
359
360 size_t
ecs_mem_usage_pr_size(void)361 ecs_mem_usage_pr_size(void)
362 {
363 return 0;
364 }
365
366 #endif /* ECS_OS_Linux, ... */
367
368 /*
369 * \brief Return maximum process memory use (in kB) depending on OS.
370 *
371 * The returned value is the maximum returned by ecs_mem_usage_pr_size()
372 * during the program's lifetime. With memory allocations which return
373 * memory to the system (such as the GNU glibc on Linux systems),
374 * this value will be correct only if allocation is tracked. This should
375 * be the case if malloc hooks are used with the glibc allocation
376 * functions (ECS library's default configuration/installation option),
377 * but may give results lower than the true maximum in other cases.
378 */
379
380 size_t
ecs_mem_usage_max_pr_size(void)381 ecs_mem_usage_max_pr_size(void)
382 {
383 (void) ecs_mem_usage_pr_size();
384
385 return _ecs_mem_usage_global_max_pr;
386 }
387
388 /*----------------------------------------------------------------------------*/
389
390 END_C_DECLS
391