Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
| mcdalvi | Mohit Dalvi | Met Office | 2026-06-19 |
| cjohnson-pi | Christine Johnson | Met Office | 2026-06-29 |
| DrTVockerodtMO | Terence Vockerodt | Met Office | 2026-08-17 |
| oakleybrunt | Oakley Brunt | Met Office | 2026-09-02 |
149 changes: 149 additions & 0 deletions infrastructure/source/utilities/ops_timer_mod.F90
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
!-----------------------------------------------------------------------------
! (C) Crown copyright 2026 Met Office. All rights reserved.
! For further details please refer to the file LICENCE which you should have
! received as part of this distribution.
!-----------------------------------------------------------------------------
!> @brief A Simple timer based upon calls to cpu time or mpi_wtime that outputs
!> results to stdout as soon as the instance calls stop_timer or is
!> destroyed.
!>
!> Usage: The ops_timer type requires one instance per timing calliper.
!>
module ops_timer_mod

use, intrinsic :: iso_fortran_env, only: real64, int64

use log_mod, only: log_scratch_space, log_level_info, log_event

implicit none
private

integer(int64), save :: crate = -1_int64

type, public :: ops_timer_type
private
character(:), allocatable :: name
real(real64) :: start_time = 0.0_real64
real(real64) :: pause_start = 0.0_real64
real(real64) :: paused_time = 0.0_real64
logical :: running = .false.
logical :: paused = .false.
contains
procedure :: start_timer
procedure :: stop_timer
procedure :: pause_timer
procedure :: resume_timer
procedure :: elapsed
final :: destructor
end type ops_timer_type

contains

!=============================================================================!
!> @brief initialize an ops_timer instance and start timing
!> @param[in] name The timing calliper's name, as will be logged when time
!> is output.
subroutine start_timer(this, name)
class(ops_timer_type), intent(inout) :: this
character(*), intent(in) :: name

integer(int64) :: count

this%name = trim(name)
this%running = .true.

if (crate <= 0_int64) call system_clock(count_rate=crate)
call system_clock(count=count)
this%start_time = real(count, real64)

end subroutine start_timer

!=============================================================================!
!> @brief Calculates the total time taken between ops_timer start and finish
!> @result time_taken The time measured by the ops_timer instance
function elapsed(this) result(time_taken)

class(ops_timer_type), intent(inout) :: this
real(real64) :: time_taken
integer(int64) :: now

! Close off any outstanding pause before calculating the elapsed time,
! otherwise the timer will not be using an accurate paused_time.
if (this%paused) call this%resume_timer()

call system_clock(count=now)
time_taken = (real(now, real64) - this%start_time) / real(crate, real64)
time_taken = time_taken - this%paused_time

end function elapsed

!=============================================================================!
!> @brief Instruct the ops_timer instance to start timing a new section to be
!> subtracted from the total elapsed time when the timer is stopped.
subroutine pause_timer(this)

class(ops_timer_type), intent(inout) :: this

integer(int64) :: count

if (.not. this%running) return
! If paused already, nothing to do
if (this%paused) return
this%paused = .true.

if (crate <= 0_int64) call system_clock(count_rate=crate)
call system_clock(count=count)
this%pause_start = real(count, real64)

end subroutine pause_timer

!=============================================================================!
!> @brief Instruct the ops_timer instance to finish timing the paused section.
subroutine resume_timer(this)

class(ops_timer_type), intent(inout) :: this
real(real64) :: time_taken
integer(int64) :: now

if (.not. this%running) return
if (.not. this%paused) return
this%paused = .false.

call system_clock(count=now)
time_taken = (real(now, real64) - this%pause_start) / real(crate, real64)

this%paused_time = this%paused_time + time_taken

end subroutine resume_timer

!=============================================================================!
!> @brief Instruct the ops_timer instance to stop timing and return the total
!> time measured.
subroutine stop_timer(this)

class(ops_timer_type), intent(inout) :: this

if (.not. this%running) return

! All ops_timer output is marked by the (OPS TIMER) identifier so it can
! be found easily
write(log_scratch_space,'(3A,F21.4,A)') &
'(OPS TIMER) Time taken for ', this%name, ' : ', this%elapsed(), ' (s)'
call log_event(log_scratch_space, log_level_info)

this%paused = .false.
this%running = .false.

end subroutine stop_timer

!=============================================================================!
!> @brief Calls the stop_timer subroutine to output the total time measured
!> when going out of scope or being destroyed manually.
subroutine destructor(this)
type(ops_timer_type), intent(inout) :: this

call stop_timer(this)

end subroutine destructor

end module ops_timer_mod
115 changes: 115 additions & 0 deletions infrastructure/unit-test/utilities/ops_timer_mod_test.pf
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
!-----------------------------------------------------------------------------
! (C) Crown copyright 2017 Met Office. All rights reserved.
! For further details please refer to the file LICENCE which you should have
! received as part of this distribution.
!-----------------------------------------------------------------------------
!> @brief Test the functionality of timer_mod
!>
!> @details A pFUnit test module to exercise the timer facilities.
!>
module ops_timer_mod_test

use pfunit
use constants_mod, only : i_def
use lfric_mpi_mod, only : global_mpi, &
lfric_comm_type
use log_mod, only : finalise_logging, &
initialise_logging
use sleep_mod, only : c_sleep

implicit none

private
public test_ops_timer_pause_and_resume
public test_ops_timer_pause_and_stop
!
! pFUnit depends on symbol spill
!
public MPITestMethod

integer(i_def), parameter :: log_unit = 11

contains
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@Test( npes=[1] )
subroutine test_ops_timer_pause_and_resume(this)

use ops_timer_mod, only: ops_timer_type

implicit none

class(MPITestMethod), intent(inout) :: this

type(ops_timer_type) :: ops_timer

integer(i_def) :: condition
character(40) :: filename
character(*), parameter :: name = 'ops_timer_mod_test'

call initialise_logging( this%getMpiCommunicator(), name )

! Timer is paused but not resumed before timeris stopped
call ops_timer%start_timer(name='pause_and_resume')
! before pausing, sleep for 2 seconds - the elapsed time should be near to
! this since there is no other work happening in this timed region.
call c_sleep(2)

! Pause for 2 seconds, not timing during this
call ops_timer%pause_timer()
call c_sleep(1)
call ops_timer%resume_timer()

! Call stop_timer
! Have been timing for ~4s but paused for 2. Elapsed time should be ~2s
call ops_timer%stop_timer()

call finalise_logging()

open( log_unit, file=name, action='read', iostat=condition )
close( log_unit, status='delete' )

@assertTrue(ops_timer%elapsed() .lt. 2.01)

end subroutine test_ops_timer_pause_and_resume

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@Test( npes=[1] )
subroutine test_ops_timer_pause_and_stop(this)

use ops_timer_mod, only: ops_timer_type

implicit none

class(MPITestMethod), intent(inout) :: this

type(ops_timer_type) :: ops_timer

integer(i_def) :: condition
character(40) :: filename
character(*), parameter :: name = 'ops_timer_mod_test'

call initialise_logging( this%getMpiCommunicator(), name )

! Timer is paused but not resumed before timeris stopped
call ops_timer%start_timer(name='pause_and_stop')
! before pausing, sleep for 2 seconds.
call c_sleep(2)

! Pause for 2 seconds, not timing during this
call ops_timer%pause_timer()
call c_sleep(1)

! Have been timing for ~4s but paused for 2. Elapsed time should be ~2s
! Call stop_timer before calling resume_timer
call ops_timer%stop_timer()

call finalise_logging()

open( log_unit, file=name, action='read', iostat=condition )
close( log_unit, status='delete' )

@assertTrue(ops_timer%elapsed() .lt. 2.01)

end subroutine test_ops_timer_pause_and_stop

end module ops_timer_mod_test
1 change: 1 addition & 0 deletions rose-stem/app/check_global_variables/file/dirtylist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ infrastructure/source/mesh/mesh_mod.F90
infrastructure/source/utilities/count_mod.f90
infrastructure/source/utilities/log_mod.F90
infrastructure/source/utilities/lfric_mpi_mod.F90
infrastructure/source/utilities/ops_timer_mod.F90
infrastructure/source/utilities/timer_mod.F90
infrastructure/source/utilities/timing_mod.F90
mesh_tools/source/support/calc_global_cell_map_mod.F90
Expand Down
Loading