From 0003054cb39a94bd3af16edf98ea26c22c33e2e0 Mon Sep 17 00:00:00 2001 From: Miles Curry <2590700+MiCurry@users.noreply.github.com> Date: Wed, 11 Mar 2026 12:17:24 -0600 Subject: [PATCH] Introduce ModEM getrusage module to record high-water memory mark Currently, the ModEM_memory module is not used anywhere in ModEM. This commit adds two new files to ModEM, ModEM_memory.f90 and ModEM_getrusage.c. ModEM_getrusage.c is a C file that calls the getrusage system call (see `man 2 getrusage`) and returns `ru_maxrss`, the maximum resident set size, or a max used memory high-water mark. ModEM_memory.f90 contains routines to either log or print `ru_maxrss`, when it does so it conveniently prints the maxrss in bytes, KB, MB and GB and allows the developer to specify a unique message with the output. It also contains a subroutine for the main task to calculate the total maxrss across tasks by using MPI_reduce. Of course, all tasks have to coordinate and call this routine at the same time. Only the main task writes out the result. Because this commit contains C code, I have coded it in a way that it can only be compiled by CMake and if `-DMODEM_MEMORY=on` is specified in the CMake build command. If this module is used in ModEM, and the user builds with the `./configure` build system, `ModEM_memory_get_maxrss` will throw an error telling the user that the C code is not compiled and how to compile it. --- CMakeLists.txt | 6 ++ f90/UTILS/CMakeLists.txt | 7 ++ f90/UTILS/ModEM_getrusage.c | 38 ++++++++++ f90/UTILS/ModEM_memory.f90 | 143 ++++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 f90/UTILS/ModEM_getrusage.c create mode 100644 f90/UTILS/ModEM_memory.f90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 730eba4a..4b47ecea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,7 @@ enable_language(Fortran) option(BUILD_MPI "Build MPI version of MPI" ON) option(FG "Build the Fine-Grained ModEM Version - (Only compatiable with SP2)" OFF) option(USE_C_TIMERS "Use the C timers in ModEM_timers" OFF) +option(MODEM_MEMORY "Compile the ModEM Memory utilitie and C memory routines" OFF) set(FORWARD_FORMULATION "SP2" CACHE STRING "Set the forward formulation: < MF | SP | SP2 >") set(MODEM_BUILD_DIMS "3D" CACHE STRING "Specify whether to build either the 2D or 3D versino of ModEM < 2D | 3D >") @@ -31,6 +32,11 @@ if (USE_C_TIMERS) enable_language(C) endif() +if (MODEM_MEMORY) + enable_language(C) + add_compile_definitions(MODEM_MEMORY) +endif() + if (NOT BUILD_GPU IN_LIST SUPPORTED_GPU_OPTS) message(FATAL_ERROR "-DBUILD_GPU option ('${BUILD_GPU}') was not valid - choose from: ${SUPPORTED_GPU_OPTS}") endif() diff --git a/f90/UTILS/CMakeLists.txt b/f90/UTILS/CMakeLists.txt index 6977d9bf..86875931 100644 --- a/f90/UTILS/CMakeLists.txt +++ b/f90/UTILS/CMakeLists.txt @@ -18,3 +18,10 @@ if (USE_C_TIMERS) add_compile_definitions(USE_C_TIMERS) endif() + +if (MODEM_MEMORY) + target_sources(${MODEM_EXE} + PRIVATE ModEM_memory.f90 + PRIVATE ModEM_getrusage.c + ) +endif() diff --git a/f90/UTILS/ModEM_getrusage.c b/f90/UTILS/ModEM_getrusage.c new file mode 100644 index 00000000..edd61e0a --- /dev/null +++ b/f90/UTILS/ModEM_getrusage.c @@ -0,0 +1,38 @@ +#include +#include + +/* It should be noted that the below function might have different + * results depending on the system you are on. POSIX doesn't guarantee + * ru_maxrss even be implemented. + * + * It's on most systems, but your results may very. + */ + +/* +* To call this C function from Fortran, use this interface: +* +* interface +* subroutine get_maxrss() result(maxrss) bind(c) +* use iso_c_binding, only : c_long +* integer (c_long), intent(out) :: maxrss +* end subroutine get_maxrss +* end interface +*/ +void get_maxrss(long *maxrss_bytes) { + + int conversion; + struct rusage usage; + +#if defined(__APPLE_) || defined(__MACH__) + // BSD's ru_maxrss (used by Apple) is in bytes + conversion = 1.0; +#elif __linux__ + // Linux's ru_maxrss is in KiB + conversion = 1000.0; +#else + conversion = 1.0; +#endif + + getrusage(RUSAGE_SELF, &usage); + *maxrss_bytes = usage.ru_maxrss / conversion; +} diff --git a/f90/UTILS/ModEM_memory.f90 b/f90/UTILS/ModEM_memory.f90 new file mode 100644 index 00000000..6a6439f5 --- /dev/null +++ b/f90/UTILS/ModEM_memory.f90 @@ -0,0 +1,143 @@ +! module ModEM_memory +! +! Use getrusage to track memory high-water mark. +! +! This module contains helpful routines to call and +! log the results of ru_maxrss by calling the +! getrusage system call. +! +! The public routines print ru_maxrss, which is +! a high-water mark of memory. More information on +! getrusage can be found by in `man 2 getrusage`. +! + +module ModEM_memory + + use utilities + use iso_c_binding, only : c_long +#ifdef MPI + use mpi +#endif + use ModEM_logger + + implicit none + + private + + integer :: task_id + + public ModEM_memory_print_report + public ModEM_memory_log_report +#ifdef MPI + public ModEM_memory_get_all +#endif + +contains + + subroutine ModEM_memory_create_log_fname() + + end subroutine + + subroutine ModEM_memory_get_maxrss(maxrss_bytes) + + implicit none + + integer, intent(out) :: maxrss_bytes + integer (c_long) :: maxrss_bytes_c + +#ifdef MODEM_MEMORY + interface + subroutine get_maxrss(maxrss_bytes) bind(c) + use iso_c_binding, only : c_long + integer (c_long), intent(out) :: maxrss_bytes + end subroutine get_maxrss + end interface + + call get_maxrss(maxrss_bytes_c) + maxrss_bytes = maxrss_bytes_c +#else + write(0,*) "ERROR: ModEM was not built with the ModEM_getrusage.c file!" + write(0,*) "ERROR: In order to use ModEM_memory routines you must" + write(0,*) "ERROR: build ModEM with CMake and specify '-DMODEM_MEMORY=on'" + call ModEM_abort() +#endif + + end subroutine ModEM_memory_get_maxrss + + subroutine ModEM_memory_print_report(message) + + implicit none + + integer :: maxrss + character (len=*), intent(in) :: message + real :: maxrss_bytes, maxrss_kb, maxrss_mb, maxrss_gb + character (len=*), parameter :: LOG_MSG_FMT = "(I4.4, A, A, A, F18.1, A, F18.1, A, F18.1, A)" + character (len=512) :: log_message + + call ModEM_memory_get_maxrss(maxrss) + call ModEM_memory_convert_maxrss(maxrss, maxrss_kb, maxrss_mb, maxrss_gb) + + write(log_message, LOG_MSG_FMT) task_id, ' - ', trim(message), ", ", maxrss_kb, ' kb ', maxrss_mb, ' mb ', maxrss_gb, ' gb' + write(6,*) trim(log_message) + + end subroutine ModEM_memory_print_report + + subroutine ModEM_memory_convert_maxrss(maxrss_bytes, maxrss_kb, maxrss_mb, maxrss_gb) + + implicit none + + integer :: maxrss_bytes + real, intent(out) :: maxrss_kb + real, intent(out) :: maxrss_mb + real, intent(out) :: maxrss_gb + + maxrss_kb = maxrss_bytes / 1.0 + maxrss_mb = maxrss_kb / 1000.0 + maxrss_gb = maxrss_mb / 1000.1 + + end subroutine ModEM_memory_convert_maxrss + + subroutine ModEM_memory_log_report(message) + + implicit none + + character (len=*), intent(in) :: message + character (len=*), parameter :: LOG_MSG_FMT = "(A, A, F18.1, A, F18.1, A, F18.1, A)" + integer :: maxrss + real :: maxrss_kb, maxrss_mb, maxrss_gb + character (len=512) :: log_message + + call ModEM_memory_get_maxrss(maxrss) + + call ModEM_memory_convert_maxrss(maxrss, maxrss_kb, maxrss_mb, maxrss_gb) + write(log_message, LOG_MSG_FMT) trim(message), ", ", maxrss_kb, ' kb ', maxrss_mb, ' mb ', maxrss_gb, ' gb' + call ModEM_log(log_message, mainOnly=.false., flush_log=.true.) + + end subroutine ModEM_memory_log_report + +#ifdef MPI + subroutine ModEM_memory_get_all(message) + + implicit none + + character (len=*), intent(in) :: message + character (len=*), parameter :: LOG_MSG_FMT = "(A, A, F18.1, A, F18.1, A, F18.1, A)" + character (len=512) :: log_message + + integer :: maxrss, global_maxrss + real :: maxrss_kb, maxrss_mb, maxrss_gb + + call ModEM_memory_get_maxrss(maxrss) + + call MPI_reduce(maxrss, global_maxrss, 1, MPI_INTEGER, MPI_SUM, 0, MPI_COMM_WORLD, ierr) + + if (taskid == 0) then + call ModEM_memory_convert_maxrss(global_maxrss, maxrss_kb, maxrss_mb, maxrss_gb) + write(log_message, LOG_MSG_FMT) trim(message), ", ", maxrss_kb, ' kb ', maxrss_mb, ' mb ', maxrss_gb, ' gb' + call ModEM_log(log_message, mainOnly=.false., flush_log=.true.) + end if + + end subroutine ModEM_memory_get_all +#endif + +end module ModEM_memory