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