module module_NEMS_Rusage
! --------------------------------------------------------------------
! Resource usage monitoring tools
! --------------------------------------------------------------------
! Calculates resource usage differences between two calls.
! Writes out a report in nemsusage.xml
!
! HISTORY:
! 2016-11 - Trahan - creator
! --------------------------------------------------------------------
! Compiler note: iso_c_binding is an intrinsic fortran module. If
! your compiler does not understand "intrinsic" or cannot find the
! "iso_c_binding" module, then that is a bug in your compiler.
use, intrinsic :: iso_c_binding ! has to be here due to type definitions
implicit none
private
public :: NEMS_Rusage
public :: NEMS_Rusage_Start
public :: NEMS_Rusage_Stop
public :: NEMS_Rusage_Report
public :: NEMS_Rusage_Is_Valid
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Compiler note: NEMS_Rusage is a Fortran class with member
! functions. If your compiler gives an error in the procedure
! declarations or the class(NEMS_Rusage) declarations later in this
! module, then that is a bug in your compiler's Fortran 2003 support.
type NEMS_Rusage
private
! Note that all member variables have names that begin with m_
! and are private. All member procedures are public.
logical :: m_valid=.false. ! .false. means do not use this object
integer(kind=c_int64_t) :: m_start_sec=-1 ! NEMS start in seconds since reference time
integer(kind=c_int64_t) :: m_start_nsec=-1 ! NEMS start nanosecond portion
integer(kind=c_int64_t) :: m_end_sec=-1 ! NEMS end in seconds since reference time
integer(kind=c_int64_t) :: m_end_nsec=-1 ! NEMS end nanosecond portion
integer :: m_comm_world=-1 ! Global communicator for NEMS
integer :: m_comm_name=-1 ! Per-host communicator
integer :: m_comm_hosts=-1 ! Communicator used by ranks 0 of each comm_name group
integer :: m_comm_size_world=-1 ! Size of comm_world
integer :: m_comm_size_hosts=-1 ! Size of comm_hosts, valid iff m_nodemaseter
integer :: m_comm_size_name=-1 ! Size of comm_name
integer :: m_rank_world=-1 ! Rank in comm_world
logical :: m_master=.false. ! Am I rank 0 on comm_world?
logical :: m_nodemaster=.false. ! Am I rank 0 on comm_name?
character(len=:), pointer :: m_procname=>NULL()
contains
procedure, public :: start => NEMS_Rusage_Start
procedure, public :: stop => NEMS_Rusage_Stop
procedure, public :: report => NEMS_Rusage_Report
procedure, public :: is_valid => NEMS_Rusage_Is_Valid
end type NEMS_Rusage
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
interface
function nems_c_crc32(buffer,length,error) bind(c)
use iso_c_binding
implicit none
integer(kind=c_int32_t), value :: length
integer(kind=c_int32_t) :: error
character(kind=c_char) :: buffer(*)
integer(kind=c_int32_t) :: nems_c_crc32
end function nems_c_crc32
end interface
interface
subroutine nems_c_timer(sec,nsec,error) bind(c)
use iso_c_binding
implicit none
integer(kind=c_int64_t) :: sec ! seconds since reference time
integer(kind=c_int64_t) :: nsec ! nanosecond portion
integer(kind=c_int32_t) :: error ! 0 on success
end subroutine nems_c_timer
end interface
interface
subroutine nems_c_usage(utime,stime,maxrss,inblock, &
outblock,error) bind(c)
use iso_c_binding
implicit none
integer(kind=c_int64_t) :: maxrss,inblock,outblock
real(kind=c_double) :: utime,stime
integer(kind=c_int32_t) :: error ! 0 on success
end subroutine nems_c_usage
end interface
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
contains
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
logical function NEMS_Rusage_Is_Valid(ru)
implicit none
class(NEMS_Rusage), intent(inout) :: ru
NEMS_Rusage_Is_Valid=ru%m_valid
end function NEMS_Rusage_Is_Valid
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine NEMS_Rusage_Report(ru,ierr,unit)
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: Ierr
integer, optional, intent(in) :: unit
integer :: xmlunit
integer(kind=c_int64_t) :: secdiff,nsecdiff
double precision :: timediff
double precision, parameter :: nano=1e-9
real(kind=c_double) :: utime,stime
integer(kind=c_Int64_t) :: maxrss,inblock,outblock
double precision :: dmaxrss, dinblock, doutblock
ierr=-999
if(.not.ru%m_valid) then
ierr=10
return
endif
call nems_c_usage(utime,stime,maxrss,inblock,outblock,ierr)
if(ierr/=0) return
if(present(unit)) then
xmlunit=unit
open(file='nemsusage.xml',unit=unit,form='formatted')
else
open(file='nemsusage.xml',newunit=xmlunit,form='formatted')
endif
dmaxrss=dble(maxrss)/1024
dinblock=dble(inblock)
doutblock=dble(outblock)
secdiff=ru%m_end_sec-ru%m_start_sec
nsecdiff=ru%m_end_nsec-ru%m_start_nsec
if(nsecdiff<0) then
nsecdiff=1000000000-nsecdiff
secdiff=secdiff-1
endif
timediff=dble(secdiff) + nano*dble(nsecdiff)
if(ru%m_master) then
write(xmlunit,'(A)') ''
write(xmlunit,'(A)') ''
endif
call report_world_max (ru,'walltime','sec',timediff,xmlunit,ierr,&
'total runtime of NEMS, excluding MPI setup time')
call report_world_range(ru,'systime', 'percent',100*stime/(stime+utime),xmlunit,ierr,&
'maximum system time used on any one rank')
call report_world_range(ru,'usertime','percent',100*utime/(stime+utime),xmlunit,ierr,&
'percent of time in user space any one rank')
call report_by_rank(ru,'memory','MiB',dmaxrss,xmlunit,ierr,&
'maximum resident set size')
call report_nodesum_range(ru,'memory','MiB',dmaxrss,xmlunit,ierr,&
'maximum resident set size')
call report_nodesum_range(ru,'blocksread','blocks',dinblock,xmlunit,ierr,&
'number of blocks read')
call report_nodesum_range(ru,'blockswritten','blocks',doutblock,xmlunit,ierr,&
'number of blocks written')
if(ru%m_master) then
write(xmlunit,'(A)') ''
endif
end subroutine NEMS_Rusage_Report
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine report_world_max(ru,what,units,localstat,xmlunit,ierr,descr)
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: Ierr
integer, intent(in) :: xmlunit
character(len=*), intent(in) :: what, units,descr
double precision, intent(in) :: localstat
double precision :: maxstat
double precision :: sendbuf
sendbuf=localstat
call MPI_Allreduce(sendbuf,maxstat,1,MPI_DOUBLE_PRECISION,MPI_MAX,&
ru%m_comm_world,ierr)
if(ru%m_master) then
write(xmlunit,20) trim(what)
write(xmlunit,30) trim(units)
write(xmlunit,31) trim(descr)
write(xmlunit,10) 'max',maxstat
write(xmlunit,21) trim(what)
endif
20 format(' <',A,'>')
21 format(' ',A,'>')
30 format(' ',A,'')
31 format(' ',A,'')
10 format(' ',F0.9,'')
end subroutine report_world_max
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine report_world_range(ru,what,units,localstat,xmlunit,ierr,descr)
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: Ierr
integer, intent(in) :: xmlunit
character(len=*), intent(in) :: what, units,descr
double precision, intent(in) :: localstat
double precision :: sumstat,maxstat,minstat
double precision :: sendbuf
sendbuf=localstat
call MPI_Allreduce(sendbuf,sumstat,1,MPI_DOUBLE_PRECISION,MPI_SUM,&
ru%m_comm_world,ierr)
call MPI_Allreduce(sendbuf,minstat,1,MPI_DOUBLE_PRECISION,MPI_MIN,&
ru%m_comm_world,ierr)
call MPI_Allreduce(sendbuf,maxstat,1,MPI_DOUBLE_PRECISION,MPI_MAX,&
ru%m_comm_world,ierr)
if(ru%m_master) then
write(xmlunit,20) trim(what)
write(xmlunit,30) trim(units)
write(xmlunit,31) trim(descr)
write(xmlunit,10) 'min',minstat
write(xmlunit,10) 'max',maxstat
write(xmlunit,10) 'avg',sumstat/ru%m_comm_size_world
write(xmlunit,21) trim(what)
endif
20 format(' <',A,'>')
21 format(' ',A,'>')
30 format(' ',A,'')
31 format(' ',A,'')
10 format(' ',F0.9,'')
end subroutine report_world_range
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine report_by_rank(ru,what,units,localstat,xmlunit,ierr,descr)
use iso_c_binding
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: ierr
integer, intent(in) :: xmlunit
character(len=*), intent(in) :: what, units,descr
double precision, intent(in) :: localstat
! Locals:
integer :: localrank ! mpi_comm_world rank of local rank
integer :: namesize ! length of processor's name
integer :: size ! sizes required for temporary buffer
integer :: ihost ! host rank within comm_hosts
integer :: arank ! index of rank within allranks
integer :: irank ! index of rank within one host
integer :: i ! contains proof that P=NP
double precision, allocatable :: hoststat(:) ! stat for all ranks in a node
double precision, allocatable :: allstats(:) ! stat for all ranks
integer, allocatable :: hostranks(:) ! ranks in world order for one host
integer, allocatable :: allranks(:) ! ranks sorted by host, then world order
integer, allocatable :: namesizes(:) ! size of names of each host
integer, allocatable :: namedispl(:) ! mpi gatherv displacements for names
integer, allocatable :: statsizes(:) ! number of ranks per host
integer, allocatable :: statdispl(:) ! mpi gatherv displacements for ranks
character(len=:), allocatable :: allnames ! all host names concatinated
if(ru%m_nodemaster) then
allocate(hoststat(ru%m_comm_size_name))
allocate(hostranks(ru%m_comm_size_name))
else
allocate(hoststat(1))
allocate(hostranks(1))
endif
if(ru%m_master) then
allocate(allstats(ru%m_comm_size_world))
allocate(allranks(ru%m_comm_size_world))
else
allocate(allstats(1))
allocate(allranks(1))
endif
! Gather ranks and stats within one node:
call MPI_Gather(localstat,1,MPI_DOUBLE_PRECISION, &
hoststat,1,MPI_DOUBLE_PRECISION, &
0,ru%m_comm_name,ierr)
if(ierr/=0) goto 1000
call MPI_Gather(ru%m_rank_world,1,MPI_INTEGER, &
hostranks,1,MPI_INTEGER, &
0,ru%m_comm_name,ierr)
if(ierr/=0) goto 1000
! Gather names across nodes:
if(ru%m_nodemaster) then
allocate(namesizes(ru%m_comm_size_hosts))
allocate(namedispl(ru%m_comm_size_hosts+1))
namesize=len(ru%m_procname)
call MPI_Allgather(namesize,1,MPI_INTEGER,&
namesizes,1,MPI_INTEGER,&
ru%m_comm_hosts,ierr)
if(ierr/=0) goto 1000
namedispl(1)=0
do i=1,ru%m_comm_size_hosts
namedispl(i+1)=namedispl(i)+namesizes(i)
enddo
flush(6)
if(ru%m_master) then
size=namedispl(ru%m_comm_size_hosts+1)
allocate(character(len=size) :: allnames)
else
allocate(character(len=1) :: allnames)
endif
call MPI_Gatherv(ru%m_procname,namesize,MPI_CHARACTER,&
allnames,namesizes,namedispl,MPI_CHARACTER,&
0,ru%m_comm_hosts,ierr)
if(ierr/=0) goto 1000
endif
! Gather ranks and stats sorted by nodes:
if(ru%m_nodemaster) then
allocate(statsizes(ru%m_comm_size_hosts))
allocate(statdispl(ru%m_comm_size_hosts+1))
call MPI_Allgather(ru%m_comm_size_name,1,MPI_INTEGER,&
statsizes,1,MPI_INTEGER,&
ru%m_comm_hosts,ierr)
if(ierr/=0) goto 1000
statdispl(1)=0
do i=1,ru%m_comm_size_hosts
statdispl(i+1)=statdispl(i)+statsizes(i)
enddo
call MPI_Gatherv(hoststat,ru%m_comm_size_name,MPI_DOUBLE_PRECISION,&
allstats,statsizes,statdispl,MPI_DOUBLE_PRECISION,&
0,ru%m_comm_hosts,ierr)
call MPI_Gatherv(hostranks,ru%m_comm_size_name,MPI_INTEGER,&
allranks,statsizes,statdispl,MPI_INTEGER,&
0,ru%m_comm_hosts,ierr)
if(ierr/=0) goto 1000
endif
! Master writes data:
if(ru%m_master) then
10 format(' <',A,'>')
write(xmlunit,10) trim(what)
20 format(' ',A,'')
write(xmlunit,20) trim(units)
30 format(' ',A,'')
write(xmlunit,30) trim(descr)
40 format(' ')
write(xmlunit,40)
arank=0
do ihost=1,ru%m_comm_size_hosts
50 format(' ')
write(xmlunit,50) trim(allnames(namedispl(ihost)+1:namedispl(ihost+1)))
do irank=1,statsizes(ihost)
arank=arank+1
60 format(' ',F0.9,'')
write(xmlunit,60) allranks(arank),allstats(arank)
enddo
70 format(' ')
write(xmlunit,70)
enddo
80 format(' ')
write(xmlunit,80)
90 format(' ',A,'>')
write(xmlunit,90) trim(what)
endif
1000 continue ! cleanup block
! These deallocates are not needed in standard-conforming fortran,
! but are placed here to work around compiler bugs:
if(allocated(hoststat)) deallocate(hoststat)
if(allocated(hostranks)) deallocate(hostranks)
if(allocated(allstats)) deallocate(allstats)
if(allocated(allranks)) deallocate(allranks)
if(allocated(allnames)) deallocate(allnames)
if(allocated(statsizes)) deallocate(statsizes)
if(allocated(statdispl)) deallocate(statdispl)
end subroutine report_by_rank
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine report_nodesum_range(ru,what,units,localstat,xmlunit,ierr,descr)
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: Ierr
integer, intent(in) :: xmlunit
character(len=*), intent(in) :: what, units,descr
double precision, intent(in) :: localstat
double precision :: sumstat,maxstat,minstat,avgstat
double precision :: sendbuf, nodesum, sendbuf3(3), recvbuf3(3)
character*100 message
! Sum within each node:
sendbuf=localstat
call MPI_Reduce(sendbuf,nodesum,1,MPI_DOUBLE_PRECISION,MPI_SUM,&
0,ru%m_comm_name,ierr)
if(ru%m_nodemaster) then
! Get stats across all nodes using rank 0 of each node:
sendbuf=nodesum
call MPI_Allreduce(sendbuf,sumstat,1,MPI_DOUBLE_PRECISION,MPI_SUM,&
ru%m_comm_hosts,ierr)
call MPI_Allreduce(sendbuf,minstat,1,MPI_DOUBLE_PRECISION,MPI_MIN,&
ru%m_comm_hosts,ierr)
call MPI_Allreduce(sendbuf,maxstat,1,MPI_DOUBLE_PRECISION,MPI_MAX,&
ru%m_comm_hosts,ierr)
! Prepare the sendbuf for broadcasting to the rest of the
! ranks. Note that we divide by the number of hosts here since
! the other ranks do not know the number of hosts.
avgstat=sumstat/ru%m_comm_size_hosts
sendbuf3=(/ avgstat, minstat, maxstat /)
endif
! Broadcast results from rank 0 of each node to the rest of the
! node's ranks:
call MPI_Bcast(sendbuf3,3,MPI_DOUBLE_PRECISION,0,&
ru%m_comm_name,ierr)
avgstat=sendbuf3(1)
minstat=sendbuf3(2)
maxstat=sendbuf3(3)
if(ru%m_master) then
write(xmlunit,20) trim(what)
write(xmlunit,30) trim(units)
write(xmlunit,31) trim(descr)
write(xmlunit,10) 'min',minstat
write(xmlunit,10) 'max',maxstat
write(xmlunit,10) 'avg',avgstat
write(xmlunit,21) trim(what)
endif
20 format(' <',A,'>')
21 format(' ',A,'>')
30 format(' ',A,'')
31 format(' ',A,'')
10 format(' ',F0.9,'')
end subroutine report_nodesum_range
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine NEMS_Rusage_Stop(ru,ierr)
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(inout) :: ierr
ierr=-999
if(.not.ru%m_valid) then
ierr=10
return
endif
call rusage_time(ru,ru%m_end_sec,ru%m_end_nsec,ierr)
end subroutine NEMS_Rusage_Stop
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine NEMS_Rusage_Start(ru,comm_world,procname,procname_len,ierr)
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(in) :: comm_world ! Global communicator for NEMS
integer, intent(in) :: procname_len
integer, intent(inout) :: ierr ! 0 = success
character(len=procname_len), intent(in) :: procname
if(ru%m_valid .and. associated(ru%m_procname)) then
deallocate(ru%m_procname)
nullify(ru%m_procname)
endif
ru%m_valid=.true.
! Compiler note: the below line is the correct syntax for
! allocating a string. If your compiler gives an error here, then
! that is a bug in your compiler.
allocate(character(len=procname_len) :: ru%m_procname)
ru%m_procname=procname
call init_rusage_comms(ru,comm_world,procname,procname_len,ierr)
if(ierr/=0) then
deallocate(ru%m_procname)
nullify(ru%m_procname)
ru%m_valid=.false.
return
endif
call rusage_time(ru,ru%m_start_sec,ru%m_start_nsec,ierr)
if(ierr/=0) then
deallocate(ru%m_procname)
nullify(ru%m_procname)
ru%m_valid=.false.
return
endif
end subroutine NEMS_Rusage_Start
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine rusage_time(ru,sec,nsec,ierr)
use iso_c_binding
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer(kind=c_int64_t),intent(inout) :: sec,nsec
integer, intent(inout) :: ierr
ierr=-999
call MPI_Barrier(ru%m_comm_world,ierr)
if(ierr/=0) return
call nems_c_timer(sec,nsec,ierr)
end subroutine rusage_time
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine init_rusage_comms(ru,comm_world,procname,procname_len,ierr)
use mpi
implicit none
class(NEMS_Rusage), intent(inout) :: ru
integer, intent(in) :: comm_world ! Global communicator for NEMS
integer, intent(in) :: procname_len
integer, intent(inout) :: ierr ! 0 = success
character(len=procname_len), intent(in) :: procname
logical :: match
integer :: rank
ru%m_comm_world=comm_world
ierr=-999
call color_by_hash(procname(1:procname_len),procname_len,comm_world,ru%m_comm_name,ru%m_comm_hosts,ru%m_rank_world,ierr)
if(ierr/=0) then
ierr=1
endif
call MPI_Comm_size(ru%m_comm_name,ru%m_comm_size_name,ierr)
call MPI_Comm_size(ru%m_comm_hosts,ru%m_comm_size_hosts,ierr)
call MPI_Comm_size(ru%m_comm_world,ru%m_comm_size_world,ierr)
call MPI_Comm_rank(ru%m_comm_world,ru%m_rank_world,ierr)
ru%m_master = ru%m_rank_world==0
call MPI_Comm_rank(ru%m_comm_name,rank,ierr)
ru%m_nodemaster = rank==0
call check_names(procname(1:procname_len),ru%m_comm_name,ru%m_comm_hosts,match,ierr)
if(ierr/=0) then
ierr=2
endif
if(.not.match) then
ierr=3
return
endif
end subroutine init_rusage_comms
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine check_names(name,comm_name,comm_hosts,match,ierr)
!! Checks to see if all names match within each comm_names group.
!! Sets match=.true. if all names match, or .false. if any groups
!! have more than one name.
use mpi
implicit none
integer, intent(inout) :: ierr
character(len=*) :: name
integer, intent(in) :: comm_name,comm_hosts
logical, intent(out) :: match
integer :: rank
integer :: error
integer :: local_match, name_match, all_names_match
character(len=MPI_MAX_PROCESSOR_NAME) :: sendbuf
ierr=-999
match=.false.
call MPI_Comm_rank(comm_name,rank,error)
if(error/=0) then
ierr=1
return
endif
sendbuf=name
call MPI_Bcast(sendbuf,MPI_MAX_PROCESSOR_NAME,MPI_CHARACTER, &
0,comm_name,error)
if(error/=0) then
ierr=2
return
endif
! The local_match is 0 if this rank thinks the names match.
local_match=1
if(trim(name)==trim(sendbuf)) then
local_match=0
else
!call msg('MISMATCH: "'//trim(name)//'" /= "'//trim(sendbuf)//'"')
end if
!call MPI_Allreduce(local_match,all_names_match,1,MPI_INTEGER,MPI_MAX,&
! MPI_COMM_WORLD,error)
! Now all ranks for this host gather the local_match value into
! name_match:
call MPI_Allreduce(local_match,name_match,1,MPI_INTEGER,MPI_MAX,&
comm_name,error)
if(error/=0) then
ierr=3
return
endif
! Master ranks of each name gather that information for all ranks
! into all_names_match, which will be 1 if any name mismatched on
! any rank.
if(rank==0) then
call MPI_Allreduce(name_match,all_names_match,1,MPI_INTEGER,MPI_MAX,&
comm_hosts,error)
if(error/=0) then
ierr=4
return
endif
endif
! Broadcast all_names_match across all ranks in this name:
call MPI_Bcast(all_names_match,1,MPI_INTEGER,0,comm_name,ierr)
if(error/=0) then
ierr=5
return
endif
match = all_names_match==0
ierr=0
end subroutine check_names
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
subroutine color_by_hash(name,namelen,commin,comm_name,comm_hosts,rank_world,ierr)
!! Groups processors into ranks by name.
!!
!! Each group in comm_name will have the same name in each rank.
!! The first rank of each communicator will be in the same group
!! of comm_hosts. This is done using a CRC-based hash function
!! and MPI_Comm_split for better scalability. The hash function
!! is in freebsd_crc32.c and is the CRC algorithm used by the
!! freebsd kernel. Hash collisions are detected via check_names.
!! If one is detected, some "salt" is added to the name to change
!! the hash value, and the process is repeated until success (or
!! 26 failures).
use mpi
use iso_c_binding
implicit none
integer, intent(in) :: namelen ! length of input buffer
character(len=namelen), intent(in) :: name
integer, intent(inout) :: ierr
integer, intent(in) :: rank_world ! used only for mpi_comm_split key
integer, intent(in) :: commin ! global communicator
integer, intent(inout) :: comm_name ! host-specific communicator
integer, intent(inout) :: comm_hosts ! for ranks 0 in comm_name, inter-host communicator
character(len=100) :: message
integer :: itry ! hash collision counter
integer :: i ! character loop index
integer :: error ! MPI error number
character(kind=c_char) :: c_name1(namelen+1) ! name plus salt in c datatype
integer(kind=c_int32_t) :: c_crc32c ! integer hash in c datatype
integer(kind=c_int32_t) :: c_length ! namelen+1 in c datatype
integer(kind=c_int32_t) :: c_error ! error indicator in c datatype
integer :: hash ! Fortran version of c_crc32c
integer :: is_rank_0 ! 1 if rank 0 within comm_name, else 0
integer :: rank ! Rank within comm_name
logical :: match ! did the check_names report all names match?
! Salt:
character(len=26), parameter :: ctry = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
ierr=-999
do i=1,namelen
c_name1(i+1)=name(i:i)
enddo
match=.false.
hashtries: do itry=1,len(ctry)
c_name1(1) = ctry(itry:itry)
c_length=namelen+1
c_error=-999
c_crc32c=nems_c_crc32(c_name1,c_length,c_error)
if(c_error/=0) then
! Should never get here. This indicates the name is
! empty or beyond 2**31-3 bytes.
ierr=1
return
endif
hash=c_crc32c
call MPI_Comm_Split(commin,hash,rank_world,comm_name,error)
if(error/=0) then
ierr=2
return ! comm split failed
endif
call MPI_Comm_rank(comm_name,rank,error)
if(error/=0) then
ierr=3
return
endif
is_rank_0=0
if(rank==0) is_rank_0=1
call MPI_Comm_Split(commin,is_rank_0,rank_world,comm_hosts,error)
if(error/=0) then
ierr=4
return ! comm split failed
endif
call check_names(name,comm_name,comm_hosts,match,error)
if(error/=0) then
ierr=3
return
endif
if(match) exit hashtries
! We get here on hash collisions. That means the
end do hashtries
if(.not.match) then
! Gave up after too many hash collisions.
ierr=4
return
endif
ierr=0
end subroutine color_by_hash
end module module_NEMS_Rusage