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