Neko 1.99.9
A portable framework for high-order spectral element flow simulations
Loading...
Searching...
No Matches
runtime_statistics.f90
Go to the documentation of this file.
1! Copyright (c) 2024-2026, The Neko Authors
2! All rights reserved.
3!
4! Redistribution and use in source and binary forms, with or without
5! modification, are permitted provided that the following conditions
6! are met:
7!
8! * Redistributions of source code must retain the above copyright
9! notice, this list of conditions and the following disclaimer.
10!
11! * Redistributions in binary form must reproduce the above
12! copyright notice, this list of conditions and the following
13! disclaimer in the documentation and/or other materials provided
14! with the distribution.
15!
16! * Neither the name of the authors nor the names of its
17! contributors may be used to endorse or promote products derived
18! from this software without specific prior written permission.
19!
20! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24! COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31! POSSIBILITY OF SUCH DAMAGE.
32!
37 use tuple, only : tuple_i4r8_t
38 use num_types, only : dp
40 use json_module, only : json_file
41 use file, only : file_t
42 use matrix, only : matrix_t
43 use utils, only : neko_error
45 use mpi_f08, only : mpi_wtime, mpi_allreduce, mpi_in_place, &
46 mpi_double_precision, mpi_sum
47 implicit none
48 private
49
51 integer, parameter :: rt_stats_init_regions = 50
53 integer, parameter :: rt_stats_reserved_regions = 25
55 integer, parameter :: rt_stats_max_name_len = 25
56
58 private
60 character(len=RT_STATS_MAX_NAME_LEN), allocatable :: rt_stats_id(:)
62 type(stack_r8_t), allocatable :: elapsed_time(:)
64 type(stack_i4r8t2_t) :: region_timestamp
65 logical :: enabled = .false.
66 logical :: output_profile = .false.
67 contains
68 procedure, public, pass(this) :: init => runtime_stats_init
69 procedure, public, pass(this) :: free => runtime_stats_free
70 procedure, public, pass(this) :: start_region => runtime_stats_start_region
71 procedure, public, pass(this) :: end_region => runtime_stats_end_region
72 procedure, public, pass(this) :: report => runtime_stats_report
73
74 procedure, pass(this) :: find_region_id => runtime_stats_find_region_id
75 procedure, private, pass(this) :: grow => runtime_stats_grow
76 end type runtime_stats_t
77
79
80contains
81
83 subroutine runtime_stats_init(this, params)
84 class(runtime_stats_t), intent(inout) :: this
85 type(json_file), intent(inout) :: params
86 integer :: i
87
88 call this%free()
89
90 call json_get_or_default(params, 'case.runtime_statistics.enabled', &
91 this%enabled, .false.)
92 call json_get_or_default(params, &
93 'case.runtime_statistics.output_profile', &
94 this%output_profile, .false.)
95
96 if (this%enabled) then
97
98 allocate(this%rt_stats_id(rt_stats_init_regions))
99
100 this%rt_stats_id = ''
101
102 allocate(this%elapsed_time(rt_stats_init_regions))
103 do i = 1, rt_stats_init_regions
104 call this%elapsed_time(i)%init()
105 end do
106
107 call this%region_timestamp%init(100)
108
109 end if
110
111 end subroutine runtime_stats_init
112
114 subroutine runtime_stats_free(this)
115 class(runtime_stats_t), intent(inout) :: this
116 integer :: i
117
118 if (allocated(this%rt_stats_id)) then
119 deallocate(this%rt_stats_id)
120 end if
121
122 if (allocated(this%elapsed_time)) then
123 do i = 1, size(this%elapsed_time)
124 call this%elapsed_time(i)%free()
125 end do
126 deallocate(this%elapsed_time)
127 end if
128
129 call this%region_timestamp%free()
130
131 end subroutine runtime_stats_free
132
135 subroutine runtime_stats_start_region(this, name, region_id)
136 class(runtime_stats_t), intent(inout) :: this
137 character(len=*), intent(in) :: name
138 integer, optional, intent(in) :: region_id
139 type(tuple_i4r8_t) :: region_data
140 character(len=RT_STATS_MAX_NAME_LEN) :: key
141 integer :: id
142
143 if (.not. this%enabled) return
144
145 ! Names are stored truncated, so compare them truncated
146 key = name
147
148 if (present(region_id)) then
149 id = region_id
150 else
151 call this%find_region_id(key, id)
152 end if
153
154 if (id .gt. 0 .and. id .le. size(this%rt_stats_id)) then
155 if (len_trim(this%rt_stats_id(id)) .eq. 0) then
156 this%rt_stats_id(id) = key
157 else if (this%rt_stats_id(id) .ne. key) then
158 call neko_error('Profile region renamed')
159 end if
160 region_data%x = id
161 region_data%y = mpi_wtime()
162 call this%region_timestamp%push(region_data)
163 else
164 call neko_error('Invalid profiling region id')
165 end if
166
167 end subroutine runtime_stats_start_region
168
172 subroutine runtime_stats_end_region(this, name, region_id)
173 class(runtime_stats_t), intent(inout) :: this
174 character(len=*), optional, intent(in) :: name
175 integer, optional, intent(in) :: region_id
176 real(kind=dp) :: end_time, elapsed_time
177 type(tuple_i4r8_t) :: region_data
178 character(len=1024) :: error_msg
179 character(len=RT_STATS_MAX_NAME_LEN) :: key
180 integer :: id
181
182 if (.not. this%enabled) return
183
184 end_time = mpi_wtime()
185 region_data = this%region_timestamp%pop()
186
187 if (region_data%x .le. 0) then
188 call neko_error('Invalid profiling region closed')
189 end if
190
191 ! If we are given a name, check it matches the id
192 if (present(name)) then
193 ! Names are stored truncated, so compare them truncated
194 key = name
195 if (present(region_id)) then
196 id = region_id
197 else
198 call this%find_region_id(key, id)
199 end if
200
201 if (this%rt_stats_id(id) .ne. key) then
202 write(error_msg, '(A,I0,A,A,A)') 'Invalid profiler region closed (', &
203 id, ', expected: ', trim(this%rt_stats_id(id)), ')'
204 call neko_error(trim(error_msg))
205
206 else if (region_data%x .ne. id) then
207
208 write(error_msg, '(A,A,A,A,A)') 'Invalid profiler region closed (', &
209 trim(this%rt_stats_id(region_data%x)), ', expected: ', &
210 trim(this%rt_stats_id(id)), ')'
211 call neko_error(trim(error_msg))
212 end if
213 end if
214
215 elapsed_time = end_time - region_data%y
216 call this%elapsed_time(region_data%x)%push(elapsed_time)
217
218 end subroutine runtime_stats_end_region
219
221 subroutine runtime_stats_report(this)
222 class(runtime_stats_t), intent(inout) :: this
223 character(len=LOG_SIZE) :: log_buf, fmt
224 character(len=:), allocatable :: hdr
225 real(kind=dp) :: avg, std, sem, total
226 integer :: i, nsamples, ncols, nrows, col_idx
227 type(matrix_t) :: profile_data
228
229 if (.not. this%enabled) return
230
231 call neko_log%section('Runtime statistics', neko_log_quiet)
232 call neko_log%newline(neko_log_quiet)
233 write(fmt, '(A,I0,A)') '(', rt_stats_max_name_len, 'x,1x,A15,2x,A15,2x,A15)'
234 write(log_buf, fmt) 'Total time', 'Avg. time', 'Range +/-'
235 call neko_log%message(log_buf, neko_log_quiet)
236 write(log_buf, '(A)') repeat('-', rt_stats_max_name_len + 50)
237 call neko_log%message(log_buf, neko_log_quiet)
238
239 ncols = 0
240 nrows = 0
241 hdr = ''
242 do i = 1, size(this%rt_stats_id)
243 if (len_trim(this%rt_stats_id(i)) .gt. 0) then
244 nsamples = this%elapsed_time(i)%size()
245 ncols = ncols + 1
246 hdr = trim(hdr) // trim(this%rt_stats_id(i)) // ', '
247 nrows = max(nrows, nsamples)
248 if (nsamples .gt. 0) then
249 select type (region_sample => this%elapsed_time(i)%data)
250 type is (double precision)
251 total = sum(region_sample(1:nsamples))
252 call mpi_allreduce(mpi_in_place, total, 1, &
253 mpi_double_precision, mpi_sum, neko_comm)
254 total = total / pe_size
255 avg = total / nsamples
256 std = (total - avg)**2 / nsamples
257 sem = std / sqrt(real(nsamples, dp))
258 end select
259 write(fmt, '(A, I0, A)') '(A', rt_stats_max_name_len, &
260 ',1x,E15.7,2x,E15.7,2x,E15.7)'
261 write(log_buf, fmt) this%rt_stats_id(i), total, avg, &
262 2.5758_dp * sem
263 call neko_log%message(log_buf, neko_log_quiet)
264 end if
265 end if
266 end do
267
268 call neko_log%newline(neko_log_quiet)
269
270 if (this%output_profile) then
271 col_idx = 0
272 call profile_data%init(nrows, ncols)
273 do i = 1, size(this%elapsed_time)
274 if (len_trim(this%rt_stats_id(i)) .gt. 0) then
275 nsamples = this%elapsed_time(i)%size()
276 col_idx = col_idx + 1
277 if (nsamples .gt. 0) then
278 select type (region_sample => this%elapsed_time(i)%data)
279 type is (double precision)
280 profile_data%x(1:nsamples, col_idx) = &
281 region_sample(1:nsamples)
282 call mpi_allreduce(mpi_in_place, &
283 profile_data%x(1:nsamples, col_idx), nsamples, &
285 profile_data%x(1:nsamples, col_idx) = &
286 profile_data%x(1:nsamples, col_idx) / pe_size
287 end select
288 end if
289 end if
290 end do
291
292 if (pe_rank .eq. 0) then
293 block
294 type(file_t) :: profile_file
295 call profile_file%init('profile.csv')
296 call profile_file%set_header(hdr)
297 call profile_file%write(profile_data)
298 end block
299 end if
300 end if
301 call neko_log%end_section()
302
303 call profile_data%free()
304
305 end subroutine runtime_stats_report
306
312 subroutine runtime_stats_find_region_id(this, name, region_id)
313 class(runtime_stats_t), intent(inout) :: this
314 character(len=*), intent(in) :: name
315 integer, intent(out) :: region_id
316 character(len=RT_STATS_MAX_NAME_LEN) :: key
317 integer :: i
318
319 region_id = -1
320
321 if (.not. this%enabled) return
322
323 ! Names are stored truncated, so look them up truncated
324 key = name
325
326 ! Look for the region name first
327 do i = rt_stats_reserved_regions + 1, size(this%rt_stats_id)
328 if (this%rt_stats_id(i) .eq. key) then
329 region_id = i
330 exit
331 end if
332 end do
333
334 ! If found, return
335 if (region_id .ne. -1) return
336
337 ! Otherwise, look for an empty slot
338 do i = rt_stats_reserved_regions + 1, size(this%rt_stats_id)
339 if (len_trim(this%rt_stats_id(i)) .eq. 0) then
340 region_id = i
341 exit
342 end if
343 end do
344
345 ! If there is none, grow the table and take its first new slot
346 if (region_id .eq. -1) then
347 region_id = size(this%rt_stats_id) + 1
348 call this%grow()
349 end if
350
351 end subroutine runtime_stats_find_region_id
352
354 subroutine runtime_stats_grow(this)
355 class(runtime_stats_t), intent(inout) :: this
356 character(len=RT_STATS_MAX_NAME_LEN), allocatable :: tmp_id(:)
357 type(stack_r8_t), allocatable :: tmp_time(:)
358 integer :: i, n
359
360 n = size(this%rt_stats_id)
361
362 allocate(tmp_id(2 * n))
363 tmp_id(1:n) = this%rt_stats_id
364 tmp_id(n + 1:) = ''
365 call move_alloc(tmp_id, this%rt_stats_id)
366
367 ! Copy the stacks, move_alloc then frees the old ones with their data
368 allocate(tmp_time(2 * n))
369 tmp_time(1:n) = this%elapsed_time
370 do i = n + 1, 2 * n
371 call tmp_time(i)%init()
372 end do
373 call move_alloc(tmp_time, this%elapsed_time)
374
375 end subroutine runtime_stats_grow
376
377end module runtime_stats
double real
Retrieves a parameter by name or assigns a provided default value. In the latter case also adds the m...
Definition comm.F90:1
type(mpi_datatype), public mpi_real_precision
MPI type for working precision of REAL types.
Definition comm.F90:54
integer, public pe_size
MPI size of communicator.
Definition comm.F90:62
integer, public pe_rank
MPI rank.
Definition comm.F90:59
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:46
Module for file I/O operations.
Definition file.f90:34
Utilities for retrieving parameters from the case files.
Logging routines.
Definition log.f90:34
integer, parameter, public neko_log_quiet
Always.
Definition log.f90:52
type(log_t), public neko_log
Global log stream.
Definition log.f90:92
integer, parameter, public log_size
Definition log.f90:46
Defines a matrix.
Definition matrix.f90:34
integer, parameter, public dp
Definition num_types.f90:10
Runtime statistics.
subroutine runtime_stats_grow(this)
Double the number of regions, keeping the recorded ones.
integer, parameter rt_stats_max_name_len
Length a region name is stored, looked up and reported with.
subroutine runtime_stats_init(this, params)
Initialise runtime statistics.
subroutine runtime_stats_start_region(this, name, region_id)
Start measuring time for the region named name with id region_id.
subroutine runtime_stats_report(this)
Report runtime statistics for all recorded regions.
subroutine runtime_stats_free(this)
Destroy runtime statistics.
integer, parameter rt_stats_reserved_regions
Region ids reserved for regions started with an explicit id.
type(runtime_stats_t), public neko_rt_stats
subroutine runtime_stats_find_region_id(this, name, region_id)
Find or allocate a region id for the named region name.
integer, parameter rt_stats_init_regions
Initial number of regions, the table grows when it is full.
subroutine runtime_stats_end_region(this, name, region_id)
Compute elapsed time for the current region.
Implements a dynamic stack ADT.
Definition stack.f90:49
Implements a n-tuple.
Definition tuple.f90:41
Utilities.
Definition utils.f90:35
A wrapper around a polymorphic generic_file_t that handles its init. This is essentially a factory fo...
Definition file.f90:56
Mixed integer-double precision 2-tuple based stack.
Definition stack.f90:112
Double precision based stack.
Definition stack.f90:91
Mixed integer ( ) double precision ( ) 2-tuple .
Definition tuple.f90:94
#define max(a, b)
Definition tensor.cu:40