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 "cs_defs.h"
28 
29 /*-----------------------------------------------------------------------------*/
30 
31 /*
32  * Standard C library headers
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 
39 #if defined (__linux__) && defined(HAVE_SYS_STAT_H) \
40                         && defined(HAVE_SYS_TYPES_H)
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <fcntl.h>
44 
45 #elif defined(__osf__) && defined(_OSF_SOURCE) && defined(HAVE_UNISTD_H)
46 #include <fcntl.h>
47 #include <sys/types.h>
48 #include <sys/signal.h>
49 #include <sys/fault.h>
50 #include <sys/syscall.h>
51 #include <sys/procfs.h>
52 #include <unistd.h>
53 
54 #elif defined(HAVE_GETRUSAGE)
55 #include <sys/time.h>
56 #include <sys/resource.h>
57 #include <unistd.h>
58 #endif
59 
60 #if defined(HAVE_UNISTD_H) && defined(HAVE_SBRK)
61 #if 0
62 #define USE_SBRK 1
63 #elif defined (__linux__)
64 #define __USE_MISC 1
65 #endif
66 #include <unistd.h>
67 #endif
68 
69 #if defined(HAVE_MALLOC_HOOKS)
70 #include <malloc.h>
71 #endif
72 
73 #if defined(HAVE_STDDEF_H)
74 #include <stddef.h>
75 #endif
76 
77 /*
78  * Optional library and BFT headers
79  */
80 
81 #include "bft_mem_usage.h"
82 
83 /*-----------------------------------------------------------------------------*/
84 
85 BEGIN_C_DECLS
86 
87 /*=============================================================================
88  * Additional doxygen documentation
89  *============================================================================*/
90 
91 /*!
92   \file bft_mem_usage.c
93         Base memory usage information (System and Library dependent).
94 
95   The memory-usage measurement functions provided here may be system-dependent.
96   If they have not yet been ported to a given type of environment,
97   these functions should return 0.
98 
99   The user should thus check for the return values of such functions to avoid
100   reporting invalid values, but the API is guaranteed.
101 */
102 
103 /*! \cond DOXYGEN_SHOULD_SKIP_THIS */
104 
105 /*-------------------------------------------------------------------------------
106  * Local type definitions
107  *-----------------------------------------------------------------------------*/
108 
109 /*-----------------------------------------------------------------------------
110  * Local static variable definitions
111  *-----------------------------------------------------------------------------*/
112 
113 static int  _bft_mem_usage_global_initialized = 0;
114 
115 static size_t _bft_mem_usage_global_max_pr = 0;
116 static size_t _bft_mem_usage_global_max_vm = 0;
117 static size_t _bft_mem_usage_global_sl = 0;
118 
119 #if defined(USE_SBRK)
120 static void  *_bft_mem_usage_global_init_sbrk = NULL;
121 #endif
122 
123 #if defined (__linux__) && defined(HAVE_SYS_STAT_H) \
124                         && defined(HAVE_SYS_TYPES_H)
125 static int    _bft_mem_usage_proc_file_init = 0;
126 #endif
127 
128 #if defined(HAVE_MALLOC_HOOKS)
129 static __malloc_ptr_t
130 (* _bft_mem_usage_old_malloc_hook)   (size_t,
131                                       const __malloc_ptr_t);
132 static __malloc_ptr_t
133 (* _bft_mem_usage_old_realloc_hook)  (void *,
134                                       size_t,
135                                       const __malloc_ptr_t);
136 static void
137 (* _bft_mem_usage_old_free_hook)     (void *,
138                                       const __malloc_ptr_t);
139 
140 static int _bft_mem_usage_global_use_hooks = 0;
141 static size_t _bft_mem_usage_n_allocs = 0;
142 static size_t _bft_mem_usage_n_reallocs = 0;
143 static size_t _bft_mem_usage_n_frees = 0;
144 
145 #endif /* (HAVE_MALLOC_HOOKS) */
146 
147 /*-----------------------------------------------------------------------------
148  * Local function definitions
149  *-----------------------------------------------------------------------------*/
150 
151 #if defined(HAVE_MALLOC_HOOKS)
152 
153 /*
154  * Test malloc_hook function.
155  *
156  * This function does not allocate memory. When it is called, it sets
157  *  the _bft_mem_usage_global_use_hooks global counter to 1, indicating
158  * the malloc hooks are effective and may be called. This should be the
159  * usual case when linking with the glibc, except when we prelink with
160  * some specific allocation library, such as is the case when using
161  * Electric Fence.
162  *
163  * returns:
164  *   1 (NULL preferred, 1 should avoid TotalView warning).
165  */
166 
167 static __malloc_ptr_t
_bft_mem_usage_malloc_hook_test(size_t size,const __malloc_ptr_t _ptr)168 _bft_mem_usage_malloc_hook_test(size_t size,
169                                 const __malloc_ptr_t _ptr)
170 {
171   _bft_mem_usage_global_use_hooks = 1;
172   return (__malloc_ptr_t)1;
173 }
174 
175 /*
176  * Memory counting malloc_hook function.
177  *
178  * This function calls the regular malloc function, but also
179  * increments a counter for the number of calls to malloc().
180  *
181  * returns:
182  *   Pointer to allocated memory.
183  */
184 
185 static __malloc_ptr_t
_bft_mem_usage_malloc_hook(size_t size,const __malloc_ptr_t _ptr)186 _bft_mem_usage_malloc_hook(size_t size,
187                            const __malloc_ptr_t _ptr)
188 {
189   void *result;
190 
191   __malloc_hook = _bft_mem_usage_old_malloc_hook;
192 
193   /* Increment counter and call malloc */
194 
195   _bft_mem_usage_n_allocs += 1;
196 
197   result = malloc(size);
198 
199   __malloc_hook = _bft_mem_usage_malloc_hook;
200 
201   return result;
202 }
203 
204 /*
205  * Memory counting realloc_hook function.
206  *
207  * This function calls the regular realloc function, but also
208  * increments a counter for the number of calls to realloc().
209  */
210 
211 static __malloc_ptr_t
_bft_mem_usage_realloc_hook(void * ptr,size_t size,const __malloc_ptr_t _ptr)212 _bft_mem_usage_realloc_hook(void *ptr,
213                             size_t size,
214                             const __malloc_ptr_t _ptr)
215 {
216   void *result;
217 
218   /* Protect __malloc_hook as well as __realloc hook, in case
219      realloc() uses malloc(). If we do not reset __malloc_hook
220      before exiting here, the __malloc_hook may be unset after
221      a realloc() on some systems */
222 
223   __realloc_hook = _bft_mem_usage_old_realloc_hook;
224   __malloc_hook  = _bft_mem_usage_old_malloc_hook;
225 
226   /* Increment counter and call realloc */
227 
228   _bft_mem_usage_n_reallocs += 1;
229 
230   result = realloc(ptr, size);
231 
232   /* Reset hooks */
233   __realloc_hook = _bft_mem_usage_realloc_hook;
234   __malloc_hook  = _bft_mem_usage_malloc_hook;
235 
236   return result;
237 }
238 
239 /*
240  * Memory counting free_hook function.
241  *
242  * This function calls the regular free function, but also
243  * increments a counter for the number of calls to free().
244  */
245 
246 static void
_bft_mem_usage_free_hook(void * ptr,const __malloc_ptr_t _ptr)247 _bft_mem_usage_free_hook(void *ptr,
248                          const __malloc_ptr_t _ptr)
249 {
250   __free_hook = _bft_mem_usage_old_free_hook;
251 
252   /* Increment counter and call free */
253 
254   _bft_mem_usage_n_frees += 1;
255 
256   free(ptr);
257 
258   __free_hook = _bft_mem_usage_free_hook;
259 }
260 
261 /*
262  * Set this library's memory counting malloc hooks if possible.
263  */
264 
265 static void
_bft_mem_usage_set_hooks(void)266 _bft_mem_usage_set_hooks(void)
267 {
268   /* Test if hooks may really be used (i.e. if there
269      is no prelinking with some other allocation library) */
270 
271   if (_bft_mem_usage_global_use_hooks == 0) {
272 
273     static __malloc_ptr_t
274       (* old_malloc_hook) (size_t, const __malloc_ptr_t);
275     void *ptr_test;
276 
277     old_malloc_hook = __malloc_hook;
278     __malloc_hook = _bft_mem_usage_malloc_hook_test;
279     ptr_test = malloc(0);
280     __malloc_hook = old_malloc_hook;
281 
282   }
283 
284   /* Set memory counting hooks */
285 
286   if (_bft_mem_usage_global_use_hooks != 0) {
287 
288     if (__malloc_hook != _bft_mem_usage_malloc_hook) {
289       _bft_mem_usage_old_malloc_hook = __malloc_hook;
290       __malloc_hook = _bft_mem_usage_malloc_hook;
291     }
292     if (__realloc_hook != _bft_mem_usage_realloc_hook) {
293       _bft_mem_usage_old_realloc_hook = __realloc_hook;
294       __realloc_hook = _bft_mem_usage_realloc_hook;
295     }
296     if (__free_hook != _bft_mem_usage_free_hook) {
297       _bft_mem_usage_old_free_hook = __free_hook;
298       __free_hook = _bft_mem_usage_free_hook;
299     }
300 
301   }
302 }
303 
304 /*
305  * Unset this library's memory counting malloc hooks if possible.
306  */
307 
308 static void
_bft_mem_usage_unset_hooks(void)309 _bft_mem_usage_unset_hooks(void)
310 {
311   if (_bft_mem_usage_global_use_hooks != 0) {
312 
313     /* Check that the hooks set are those defined here, as
314        they may already have been replaced by another library
315        (such as some MPI libraries). */
316 
317     if (__malloc_hook == _bft_mem_usage_malloc_hook)
318       __malloc_hook = _bft_mem_usage_old_malloc_hook;
319     if (__realloc_hook == _bft_mem_usage_realloc_hook)
320       __realloc_hook = _bft_mem_usage_old_realloc_hook;
321     if (__free_hook == _bft_mem_usage_free_hook)
322       __free_hook = _bft_mem_usage_old_free_hook;
323 
324     _bft_mem_usage_global_use_hooks = 0;
325 
326     _bft_mem_usage_global_use_hooks = 0;
327     _bft_mem_usage_n_allocs = 0;
328     _bft_mem_usage_n_reallocs = 0;
329     _bft_mem_usage_n_frees = 0;
330 
331   }
332 }
333 
334 #endif /* defined(HAVE_MALLOC_HOOKS) */
335 
336 #if defined (__linux__) && defined(HAVE_SYS_STAT_H) \
337                         && defined(HAVE_SYS_TYPES_H)
338 
339 /*!
340  * \brief Initialize current process memory use count depending on system.
341  */
342 
343 static void
_bft_mem_usage_pr_size_init(void)344 _bft_mem_usage_pr_size_init(void)
345 {
346   char  buf[81]; /* should be large enough for "/proc/%lu/status"
347                     then beginning of file content */
348   bool    status_has_size = false;
349   bool    status_has_peak = false;
350   const pid_t  pid = getpid();
351 
352   /*
353     Under Linux with procfs, one line of the pseudo-file "/proc/pid/status"
354     (where pid is the process number) is of the following form:
355     VmSize:     xxxx kB
356     VmPeak:     xxxx kB
357     When both VmSize and VmPeak are available, we are able to determine
358     memory use in a robust fashion using these fields.
359   */
360 
361   if (_bft_mem_usage_proc_file_init != 0)
362     return;
363 
364   sprintf(buf, "/proc/%lu/status", (unsigned long) pid);
365   FILE *fp = fopen(buf, "r");
366 
367   if (fp != NULL) {
368 
369     int fields_read = 0;
370 
371     while (fields_read < 2) {
372       char *s = fgets(buf, 80, fp);
373       if (s == NULL)
374         break;
375       if (strncmp(s, "VmSize:", 7) == 0) {
376         status_has_size = true;
377         fields_read += 1;
378       }
379       else if (strncmp(s, "VmPeak:", 7) == 0) {
380         status_has_peak = true;
381         fields_read += 1;
382       }
383     }
384 
385     /* If VmSize was found, proc file may be used */
386     if (status_has_peak && status_has_size)
387       _bft_mem_usage_proc_file_init = 1;
388 
389     fclose(fp);
390   }
391 
392   /* If initialization failed for some reason (proc file unavailable or does
393      or does not contain the required fields), mark method as unusable */
394   if (_bft_mem_usage_proc_file_init == 0)
395     _bft_mem_usage_proc_file_init = -1;
396 }
397 
398 #else  /* defined (__linux__) && ... */
399 
400 #define _bft_mem_usage_pr_size_init()
401 
402 #endif /* defined (__linux__) && ... */
403 
404 /*! (DOXYGEN_SHOULD_SKIP_THIS) \endcond */
405 
406 /*============================================================================
407  * Public function definitions
408  *============================================================================*/
409 
410 /*!
411  * \brief Initialize memory usage count depending on system.
412  *
413  * This functions checks if it has already been called, so
414  * it is safe to call more than once (though it is not
415  * thread-safe). Only the first call is effective.
416  */
417 
418 void
bft_mem_usage_init(void)419 bft_mem_usage_init(void)
420 {
421   if (_bft_mem_usage_global_initialized != 0)
422     return;
423 
424 #if defined(USE_SBRK)
425 
426   /*
427     We use sbrk() to know the size of the heap. This is not of any use
428     to guess at allocated memory when some part of the memory may
429     be allocated with mmap(), such as with glibc on Linux.
430   */
431 
432   _bft_mem_usage_global_init_sbrk = (void *) sbrk(0);
433 
434 #endif /* (USE_SBRK) */
435 
436 #if defined(HAVE_MALLOC_HOOKS)
437 
438   _bft_mem_usage_set_hooks();
439 
440 #endif
441 
442   _bft_mem_usage_global_initialized = 1;
443 }
444 
445 /*!
446  * \brief End memory usage count depending on system.
447  */
448 
449 void
bft_mem_usage_end(void)450 bft_mem_usage_end(void)
451 {
452 #if defined(HAVE_MALLOC_HOOKS)
453   _bft_mem_usage_unset_hooks();
454 #endif
455 }
456 
457 /*!
458  * \brief Indicates if bft_mem_usage_...() functions are initialized.
459  *
460  * \returns 1 if bft_mem_usage_init has been called, 0 otherwise.
461  */
462 
463 int
bft_mem_usage_initialized(void)464 bft_mem_usage_initialized(void)
465 {
466   return _bft_mem_usage_global_initialized;
467 }
468 
469 /*!
470  * \brief Return current process memory use (in kB) depending on system.
471  *
472  * If the information is not available (depending on availability of
473  * non-portable function calls), 0 is returned.
474  */
475 
476 #if defined (__linux__) && defined(HAVE_SYS_STAT_H) \
477                         && defined(HAVE_SYS_TYPES_H)
478 
479 size_t
bft_mem_usage_pr_size(void)480 bft_mem_usage_pr_size(void)
481 {
482   size_t sys_mem_usage = 0;
483 
484   /*
485     Under Linux with procfs, one line of the pseudo-file "/proc/pid/status"
486     (where pid is the process number) is of the following form:
487     VmSize:     xxxx kB
488     With more recent kernels, we also have line of the form:
489     VmPeak:     xxxx kB
490     VmHWM:      xxxx kB
491     VmLib:      xxxx kB
492     Representing peak virtual memory, peak resident set size,
493     and shares library  usage respectively.
494   */
495 
496   if (_bft_mem_usage_proc_file_init == 0)
497     _bft_mem_usage_pr_size_init();
498 
499   if (_bft_mem_usage_proc_file_init == 1) {
500 
501     char  buf[81]; /* should be large enough for "/proc/%lu/status" */
502     const pid_t  pid = getpid();
503 
504     unsigned long val;
505 
506     sprintf(buf, "/proc/%lu/status", (unsigned long) pid);
507     FILE *fp = fopen(buf, "r");
508 
509     if (fp != NULL) {
510 
511       int fields_read = 0;
512 
513       while (fields_read < 4) {
514         char *s = fgets(buf, 80, fp);
515         if (s == NULL)
516           break;
517         if (strncmp(s, "VmSize:", 7) == 0) {
518           sscanf (s + 7, "%lu", &val);
519           sys_mem_usage = (size_t) val;
520           fields_read += 1;
521         }
522         else if (strncmp(s, "VmHWM:", 6) == 0) {
523           sscanf (s + 6, "%lu", &val);
524           if ((size_t) val > _bft_mem_usage_global_max_pr)
525             _bft_mem_usage_global_max_pr = (size_t) val;
526           fields_read += 1;
527         }
528         else if (strncmp(s, "VmPeak:", 7) == 0) {
529           sscanf (s + 7, "%lu", &val);
530           if ((size_t) val > _bft_mem_usage_global_max_vm)
531             _bft_mem_usage_global_max_vm = (size_t) val;
532           fields_read += 1;
533         }
534         else if (strncmp(s, "VmLib:", 6) == 0) {
535           sscanf (s + 6, "%lu", &val);
536           if ((size_t) val > _bft_mem_usage_global_sl)
537             _bft_mem_usage_global_sl = (size_t) val;
538           fields_read += 1;
539         }
540       }
541 
542       fclose(fp);
543 
544     } /* End of condition on "VmSize:" and "VmPeak:" availability */
545 
546   }
547 
548   if (sys_mem_usage > _bft_mem_usage_global_max_pr)
549     _bft_mem_usage_global_max_pr = sys_mem_usage;
550 
551   return sys_mem_usage;
552 }
553 
554 #elif defined(USE_SBRK)
555 
556 size_t
bft_mem_usage_pr_size(void)557 bft_mem_usage_pr_size(void)
558 {
559   size_t alloc_size = 0;
560 
561   if (_bft_mem_usage_global_initialized) {
562     void    *end_addr;
563 
564     end_addr = (void *) sbrk(0);
565 
566 #if defined(HAVE_PTRDIFF_T)
567     alloc_size = (size_t)(  (ptrdiff_t)end_addr
568                           - (ptrdiff_t)_bft_mem_usage_global_init_sbrk) / 1024;
569 #else
570     alloc_size = (end_addr - _bft_mem_usage_global_init_sbrk) / 1024;
571 #endif
572 
573   }
574 
575   if (alloc_size > _bft_mem_usage_global_max_pr)
576     _bft_mem_usage_global_max_pr = alloc_size;
577 
578   return alloc_size;
579 }
580 
581 #elif defined(HAVE_GETRUSAGE)
582 
583 size_t
bft_mem_usage_pr_size(void)584 bft_mem_usage_pr_size(void)
585 {
586   size_t sys_mem_usage = 0;
587   struct rusage usage;
588 
589   getrusage(RUSAGE_SELF, &usage);
590 
591   sys_mem_usage = usage.ru_maxrss / 1024;
592 
593   return sys_mem_usage;
594 }
595 
596 #else /* Default case */
597 
598 size_t
bft_mem_usage_pr_size(void)599 bft_mem_usage_pr_size(void)
600 {
601   return 0;
602 }
603 
604 #endif /* __linux__, __osf__, ... */
605 
606 /*
607  * \brief Return maximum process memory use (in kB) depending on OS.
608  *
609  * The returned value is the maximum memory used during the program's
610  * lifetime.
611  *
612  * \return maximum measured program size, or 0 if not available
613  */
614 
615 size_t
bft_mem_usage_max_pr_size(void)616 bft_mem_usage_max_pr_size(void)
617 {
618   (void) bft_mem_usage_pr_size();
619 
620   return _bft_mem_usage_global_max_pr;
621 }
622 
623 /*
624  * \brief Return maximum process virtual memory use (in kB) depending on OS.
625  *
626  * \return  maximum measured virtual memory usage, or 0 if not available
627  */
628 
629 size_t
bft_mem_usage_max_vm_size(void)630 bft_mem_usage_max_vm_size(void)
631 {
632   (void) bft_mem_usage_pr_size();
633 
634   return _bft_mem_usage_global_max_vm;
635 }
636 
637 /*
638  * \brief Return shared library memory use (in kB) depending on OS.
639  *
640  * \return  maximum measured shared library memory usage,
641  *          or 0 if not available
642  */
643 
644 size_t
bft_mem_usage_shared_lib_size(void)645 bft_mem_usage_shared_lib_size(void)
646 {
647   (void) bft_mem_usage_pr_size();
648 
649   return _bft_mem_usage_global_sl;
650 }
651 
652 /*
653  * \brief Return counter to number of calls to malloc, realloc, and free.
654  *
655  * This function returns zeroes when the appropriate instrumentation
656  * is not available.
657  */
658 
659 void
bft_mem_usage_n_calls(size_t count[3])660 bft_mem_usage_n_calls(size_t count[3])
661 {
662 #if defined(HAVE_MALLOC_HOOKS)
663   count[0] = _bft_mem_usage_n_allocs;
664   count[1] = _bft_mem_usage_n_reallocs;
665   count[2] = _bft_mem_usage_n_frees;
666 #else
667   CS_UNUSED(count);
668 #endif /* (HAVE_MALLOC_HOOKS) */
669 }
670 
671 /*----------------------------------------------------------------------------*/
672 
673 END_C_DECLS
674