Neko 1.99.1
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, 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!
35 use logger, only : neko_log, log_size
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
44 use comm, only : pe_rank, pe_size, neko_comm
45 use mpi_f08, only : mpi_wtime, mpi_allreduce, mpi_in_place, &
46 mpi_double_precision, mpi_sum
47 implicit none
48 private
49
50 integer, parameter :: rt_stats_max_regions = 50
51 integer, parameter :: rt_stats_reserved_regions = 25
52 integer, parameter :: rt_stats_max_name_len = 25
53
55 private
57 character(len=RT_STATS_MAX_NAME_LEN), allocatable :: rt_stats_id(:)
59 type(stack_r8_t), allocatable :: elapsed_time(:)
61 type(stack_i4r8t2_t) :: region_timestamp
62 logical :: enabled = .false.
63 logical :: output_profile = .false.
64 contains
65 procedure, public, pass(this) :: init => runtime_stats_init
66 procedure, public, pass(this) :: free => runtime_stats_free
67 procedure, public, pass(this) :: start_region => runtime_stats_start_region
68 procedure, public, pass(this) :: end_region => runtime_stats_end_region
69 procedure, public, pass(this) :: report => runtime_stats_report
70
71 procedure, pass(this) :: find_region_id => runtime_stats_find_region_id
72 end type runtime_stats_t
73
75
76contains
77
79 subroutine runtime_stats_init(this, params)
80 class(runtime_stats_t), intent(inout) :: this
81 type(json_file), intent(inout) :: params
82 integer :: i
83
84 call this%free()
85
86 call json_get_or_default(params, 'case.runtime_statistics.enabled', &
87 this%enabled, .false.)
88 call json_get_or_default(params, &
89 'case.runtime_statistics.output_profile', &
90 this%output_profile, .false.)
91
92 if (this%enabled) then
93
94 allocate(this%rt_stats_id(rt_stats_max_regions))
95
96 this%rt_stats_id = ''
97
98 allocate(this%elapsed_time(rt_stats_max_regions))
99 do i = 1, rt_stats_max_regions
100 call this%elapsed_time(i)%init()
101 end do
102
103 call this%region_timestamp%init(100)
104
105 end if
106
107 end subroutine runtime_stats_init
108
110 subroutine runtime_stats_free(this)
111 class(runtime_stats_t), intent(inout) :: this
112 integer :: i
113
114 if (allocated(this%rt_stats_id)) then
115 deallocate(this%rt_stats_id)
116 end if
117
118 if (allocated(this%elapsed_time)) then
119 do i = 1, size(this%elapsed_time)
120 call this%elapsed_time(i)%free()
121 end do
122 deallocate(this%elapsed_time)
123 end if
124
125 call this%region_timestamp%free()
126
127 end subroutine runtime_stats_free
128
131 subroutine runtime_stats_start_region(this, name, region_id)
132 class(runtime_stats_t), intent(inout) :: this
133 character(len=*), intent(in) :: name
134 integer, optional, intent(in) :: region_id
135 type(tuple_i4r8_t) :: region_data
136 integer :: id
137
138 if (.not. this%enabled) return
139
140 if (present(region_id)) then
141 id = region_id
142 else
143 call this%find_region_id(name, id)
144 end if
145
146 if (id .gt. 0 .and. id .le. rt_stats_max_regions) then
147 if (len_trim(this%rt_stats_id(id)) .eq. 0) then
148 this%rt_stats_id(id) = trim(name)
149 else if (trim(this%rt_stats_id(id)) .ne. trim(name)) then
150 call neko_error('Profile region renamed')
151 end if
152 region_data%x = id
153 region_data%y = mpi_wtime()
154 call this%region_timestamp%push(region_data)
155 else
156 call neko_error('Invalid profiling region id')
157 end if
158
159 end subroutine runtime_stats_start_region
160
164 subroutine runtime_stats_end_region(this, name, region_id)
165 class(runtime_stats_t), intent(inout) :: this
166 character(len=*), optional, intent(in) :: name
167 integer, optional, intent(in) :: region_id
168 real(kind=dp) :: end_time, elapsed_time
169 type(tuple_i4r8_t) :: region_data
170 character(len=1024) :: error_msg
171 integer :: id
172
173 if (.not. this%enabled) return
174
175 end_time = mpi_wtime()
176 region_data = this%region_timestamp%pop()
177
178 if (region_data%x .le. 0) then
179 call neko_error('Invalid profiling region closed')
180 end if
181
182 ! If we are given a name, check it matches the id
183 if (present(name)) then
184 if (present(region_id)) then
185 id = region_id
186 else
187 call this%find_region_id(name, id)
188 end if
189
190 if (trim(this%rt_stats_id(id)) .ne. trim(name)) then
191 write(error_msg, '(A,I0,A,A,A)') 'Invalid profiler region closed (', &
192 id, ', expected: ', trim(this%rt_stats_id(id)), ')'
193 call neko_error(trim(error_msg))
194
195 else if (region_data%x .ne. id) then
196
197 write(error_msg, '(A,A,A,A,A)') 'Invalid profiler region closed (', &
198 trim(this%rt_stats_id(region_data%x)), ', expected: ', &
199 trim(this%rt_stats_id(id)), ')'
200 call neko_error(trim(error_msg))
201 end if
202 end if
203
204 elapsed_time = end_time - region_data%y
205 call this%elapsed_time(region_data%x)%push(elapsed_time)
206
207 end subroutine runtime_stats_end_region
208
210 subroutine runtime_stats_report(this)
211 class(runtime_stats_t), intent(inout) :: this
212 character(len=LOG_SIZE) :: log_buf, fmt
213 character(len=1250) :: hdr
214 real(kind=dp) :: avg, std, sem, total
215 integer :: i, nsamples, ncols, nrows, col_idx
216 type(matrix_t) :: profile_data
217
218 if (.not. this%enabled) return
219
220 call neko_log%section('Runtime statistics')
221 call neko_log%newline()
222 write(fmt, '(A,I0,A)') '(', rt_stats_max_name_len, 'x,1x,A15,2x,A15,2x,A15)'
223 write(log_buf, fmt) 'Total time', 'Avg. time', 'Range +/-'
224 call neko_log%message(log_buf)
225 write(log_buf, '(A)') repeat('-', rt_stats_max_name_len + 50)
226 call neko_log%message(log_buf)
227
228 ncols = 0
229 nrows = 0
230 hdr = ''
231 do i = 1, rt_stats_max_regions
232 if (len_trim(this%rt_stats_id(i)) .gt. 0) then
233 nsamples = this%elapsed_time(i)%size()
234 ncols = ncols + 1
235 hdr = trim(hdr) // trim(this%rt_stats_id(i)) // ', '
236 nrows = max(nrows, nsamples)
237 if (nsamples .gt. 0) then
238 select type (region_sample => this%elapsed_time(i)%data)
239 type is (double precision)
240 total = sum(region_sample(1:nsamples))
241 call mpi_allreduce(mpi_in_place, total, 1, &
242 mpi_double_precision, mpi_sum, neko_comm)
243 total = total / pe_size
244 avg = total / nsamples
245 std = (total - avg)**2 / nsamples
246 sem = std /sqrt(real(nsamples, dp))
247 end select
248 write(fmt, '(A,I0,A)') '(A', rt_stats_max_name_len, &
249 ',1x,E15.7,2x,E15.7,2x,E15.7)'
250 write(log_buf, fmt) this%rt_stats_id(i), total, avg, &
251 2.5758_dp * sem
252 call neko_log%message(log_buf)
253 end if
254 end if
255 end do
256
257 call neko_log%newline()
258
259 if (this%output_profile) then
260 col_idx = 0
261 call profile_data%init(nrows, ncols)
262 do i = 1, size(this%elapsed_time)
263 if (len_trim(this%rt_stats_id(i)) .gt. 0) then
264 nsamples = this%elapsed_time(i)%size()
265 col_idx = col_idx + 1
266 if (nsamples .gt. 0) then
267 select type (region_sample => this%elapsed_time(i)%data)
268 type is (double precision)
269 profile_data%x(1:nsamples,col_idx) = &
270 region_sample(1:nsamples)
271 call mpi_allreduce(mpi_in_place, &
272 profile_data%x(1:nsamples,col_idx), nsamples, &
273 mpi_double_precision, mpi_sum, neko_comm)
274 profile_data%x(1:nsamples, col_idx) = &
275 profile_data%x(1:nsamples, col_idx) / pe_size
276 end select
277 end if
278 end if
279 end do
280
281 if (pe_rank .eq. 0) then
282 block
283 type(file_t) :: profile_file
284 call profile_file%init('profile.csv')
285 call profile_file%set_header(hdr)
286 call profile_file%write(profile_data)
287 end block
288 end if
289 end if
290 call neko_log%end_section()
291
292 call profile_data%free()
293
294 end subroutine runtime_stats_report
295
297 subroutine runtime_stats_find_region_id(this, name, region_id)
298 class(runtime_stats_t), intent(inout) :: this
299 character(len=*), intent(in) :: name
300 integer, intent(out) :: region_id
301 integer :: i
302
303 region_id = -1
304
305 ! Look for the region name first
307 if (trim(this%rt_stats_id(i)) .eq. trim(name)) then
308 region_id = i
309 exit
310 end if
311 end do
312
313 ! If found, return
314 if (region_id .ne. -1) return
315
316 ! Otherwise, look for an empty slot
318 if (len_trim(this%rt_stats_id(i)) .eq. 0) then
319 region_id = i
320 exit
321 end if
322 end do
323
324 if (region_id .eq. -1) then
325 call neko_error('Not enough profiling regions available')
326 end if
327
328 end subroutine runtime_stats_find_region_id
329
330end 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
integer, public pe_size
MPI size of communicator.
Definition comm.F90:58
integer, public pe_rank
MPI rank.
Definition comm.F90:55
type(mpi_comm), public neko_comm
MPI communicator.
Definition comm.F90:42
Module for file I/O operations.
Definition file.f90:34
Utilities for retrieving parameters from the case files.
Logging routines.
Definition log.f90:34
type(log_t), public neko_log
Global log stream.
Definition log.f90:70
integer, parameter, public log_size
Definition log.f90:46
Defines a matrix.
Definition matrix.f90:34
integer, parameter, public dp
Definition num_types.f90:9
Runtime statistics.
integer, parameter rt_stats_max_name_len
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
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.
subroutine runtime_stats_end_region(this, name, region_id)
Compute elapsed time for the current region.
integer, parameter rt_stats_max_regions
Implements a dynamic stack ADT.
Definition stack.f90:35
Implements a n-tuple.
Definition tuple.f90:34
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:55
Mixed integer-double precision 2-tuple based stack.
Definition stack.f90:98
Double precision based stack.
Definition stack.f90:77
Mixed integer ( ) double precision ( ) 2-tuple .
Definition tuple.f90:87
#define max(a, b)
Definition tensor.cu:40