message_mod sourceΒΆ

  1module message_mod
  2  ! MODULE message_mod (prefix='msg' category='8. Low-level utilities and constants')
  3  !
  4  !:Purpose:  Output message interface with configurable verbosity.
  5  !           Also provides string representation for some intrinsic types.
  6  !
  7  use midasMpi_mod
  8  use utilities_mod
  9  implicit none
 10  save
 11  private
 12
 13  ! public procedures
 14  public :: msg, msg_memUsage, msg_section, msg_setVerbThreshold
 15
 16  ! public module variables
 17  integer, public, parameter :: msg_ALWAYS   = -99 ! verbosity level indicating a message is always printed irrespectively of set threshold
 18  integer, public, parameter :: msg_NEVER    =  99 ! verbosity level indicating a message is never printed irrespectively of set threshold
 19  integer, public, parameter :: msg_DEFAULT  =   1 ! default verbosity level
 20
 21  integer, public :: msg_NML = msg_DEFAULT ! verbosity level fixed from namelist
 22
 23  ! intrinsic type string representations
 24  public :: str
 25  interface str
 26    module procedure msg_str2str
 27    module procedure msg_log2str
 28    module procedure msg_int2str
 29    module procedure msg_real42str
 30    module procedure msg_real82str
 31    module procedure msg_charArray2str
 32    module procedure msg_logArray2str
 33    module procedure msg_intArray2str
 34    module procedure msg_real4Array2str
 35    module procedure msg_real8Array2str
 36  end interface
 37
 38  ! private module variables
 39  integer, parameter    :: msg_lineLen = 70
 40  integer, parameter    :: msg_num2strBufferLen = 200
 41  integer, parameter    :: msg_indent = 4
 42
 43  integer :: verbosityThreshold
 44  logical :: msg_arrayVertical
 45
 46  contains
 47  
 48  !--------------------------------------------------------------------------
 49  ! msg
 50  !--------------------------------------------------------------------------
 51  subroutine msg(origin, message, verb_opt, mpiAll_opt, separator_opt)
 52    !
 53    !:Purpose: Output message if its verbosity level is less than or equal to
 54    !           the user provided verbosity threshold (see `msg_readNml()`).
 55    !           The verbosity levels are:
 56    !
 57    !                             * msg_ALWAYS : always printed, irrespectively of the threshold
 58    !                             * 0          : critical, should always printed
 59    !                             * 1          : default priority; printed in operational context
 60    !                             * 2          : detailed output, provides extra information
 61    !                             * 3          : intended for developers, printed for debugging or specific diagnostcs
 62    !                             * msg_NEVER  : never printed, irrespectively of the threshold
 63    !
 64    implicit none
 65
 66    ! Arguments:
 67    character(len=*),           intent(in) :: origin        ! originating subroutine, function or program
 68    character(len=*),           intent(in) :: message       ! message to be printed
 69    integer,          optional, intent(in) :: verb_opt      ! maximum verbosity level of messages to be printed, defaults to 1
 70    logical,          optional, intent(in) :: mpiAll_opt    ! choose to prints to all MPI tasks (default), otherwise only task 0
 71    character(len=*), optional, intent(in) :: separator_opt ! separator string between origin and message
 72
 73    ! Locals:
 74    logical :: mpiAll
 75    integer :: verbLevel
 76
 77    if (present(verb_opt)) then
 78      verbLevel = verb_opt
 79    else
 80      verbLevel = msg_DEFAULT
 81    end if
 82
 83    if (present(mpiAll_opt)) then
 84      mpiAll = mpiAll_opt
 85    else
 86      mpiAll = .true.
 87    end if
 88
 89    if (verbLevel == msg_ALWAYS) then
 90      if (mpiAll) then
 91        call msg_write(origin, message, separator_opt=separator_opt)
 92      else
 93        if (mmpi_myid == 0) call msg_write(origin, message, separator_opt=separator_opt)
 94      end if
 95
 96    else if (verbLevel == msg_NEVER) then
 97      return
 98
 99    else
100      call msg_readNml()
101      if (verbLevel <= verbosityThreshold) then
102        if (mpiAll) then
103          call msg_write(origin, message, separator_opt=separator_opt)
104        else
105          if (mmpi_myid == 0) call msg_write(origin, message, separator_opt=separator_opt)
106        end if
107      end if
108    end if
109
110  end subroutine msg
111
112  !--------------------------------------------------------------------------
113  ! msg_memUsage
114  !--------------------------------------------------------------------------
115  subroutine msg_memUsage(origin, verb_opt, mpiAll_opt)
116    !
117    !:Purpose: Report memory usage
118    !
119    implicit none
120
121    ! Arguments:
122    character(len=*),  intent(in) :: origin     ! originating subroutine, function or program
123    integer, optional, intent(in) :: verb_opt   ! verbosity level of the message
124    logical, optional, intent(in) :: mpiAll_opt ! choose to prints to all MPI tasks (default), otherwise only task 0
125
126    ! Locals:
127    integer :: usageMb
128    integer, external :: get_max_rss
129
130    usageMb = get_max_rss()/1024
131    call msg( origin, 'Memory Used: '//str(usageMb)//' Mb', verb_opt, mpiAll_opt)
132
133  end subroutine msg_memUsage
134
135  !--------------------------------------------------------------------------
136  ! msg_section
137  !--------------------------------------------------------------------------
138  subroutine msg_section(origin, section, description_opt, verb_opt, mpiAll_opt)
139    !
140    !:Purpose: Document, both in source and runtime listings, sections of a program.
141    !
142    implicit none
143
144    ! Arguments:
145    character(len=*),           intent(in) :: origin          ! originating subroutine, function or program
146    character(len=*),           intent(in) :: section         ! section number
147    character(len=*), optional, intent(in) :: description_opt ! optional description message to be printed
148    integer,          optional, intent(in) :: verb_opt        ! verbosity level of the message
149    logical,          optional, intent(in) :: mpiAll_opt      ! choose to prints to all MPI tasks (default), otherwise only task 0
150
151    ! Locals:
152    character(len=:), allocatable :: message
153
154    message = 'SECTION '//str(section, quote_opt=.false.)
155    if (present(description_opt)) then
156      message = message//' - '//description_opt
157    end if
158    write(*,*)
159    write(*,*) repeat('_',msg_lineLen)
160    call msg(origin, message, verb_opt, mpiAll_opt, separator_opt=' >>> ')
161
162  end subroutine msg_section
163
164  !--------------------------------------------------------------------------
165  ! msg_setVerbThreshold
166  !--------------------------------------------------------------------------
167  subroutine msg_setVerbThreshold(threshold, beSilent_opt)
168    !
169    !:Purpose: Sets the maximum verbosity level of messages to be printed.
170    !
171    implicit none
172
173    ! Arguments:
174    integer,           intent(in) :: threshold    ! maximum verbosity level of messages to be printed to listing
175    logical, optional, intent(in) :: beSilent_opt ! choose to not print warning message (default .false.)
176
177    ! Locals:
178    logical :: beSilent
179
180    if (present(beSilent_opt)) then
181      beSilent = beSilent_opt
182    else
183      beSilent = .false.
184    end if
185
186    verbosityThreshold = threshold
187    if (.not. beSilent) then
188      call msg( 'msg_setVerbThreshold', 'WARNING: Setting verbosity threshold to '&
189                //str(verbosityThreshold)//' for DEBUGGING purposes.', verb_opt=msg_ALWAYS)
190    end if
191
192  end subroutine msg_setVerbThreshold
193
194  !--------------------------------------------------------------------------
195  ! msg_readNml (private)
196  !--------------------------------------------------------------------------
197  subroutine msg_readNml()
198    !
199    !:Purpose: Reads the module configuration namelist
200    !
201    !:Namelist parameters:
202    !       :verbosity:   Each call to `msg()` specifies a verbosity level;
203    !                     this threshold configures until which level
204    !                     messages will be outputed.
205    !
206    implicit none
207
208    ! Locals:
209    logical, save :: alreadyRead = .false.
210    integer :: nulnam, ierr, fnom, fclos
211
212    ! Namelist variables
213    logical :: arrayVertical  ! choose to use array vertical representation by default
214    integer :: verbosity      ! maximum verbosity level of messages to be printed in the listing
215    namelist /NAMMSG/verbosity, arrayVertical
216  
217    if (alreadyRead) then
218      return
219    else
220      alreadyRead = .true.
221    end if
222  
223    ! default namelist value
224    verbosity = msg_DEFAULT
225    arrayVertical = .false.
226  
227    if ( .not. utl_isNamelistPresent('NAMMSG','./flnml') ) then
228      call msg( 'msg_readNml', 'NAMMSG is missing in the namelist. The default values will be taken.', &
229                mpiAll_opt=.false., verb_opt=msg_ALWAYS)
230    else
231      nulnam = 0
232      ierr = fnom(nulnam, 'flnml','FTN+SEQ+R/O',0)
233      read(nulnam,nml=nammsg,iostat=ierr)
234      if (ierr /= 0) call utl_abort('msg_readNml: Error reading namelist NAMMSG')
235      if (mmpi_myid == 0) write(*,nml=nammsg)
236      ierr = fclos(nulnam)
237    end if
238    msg_NML = verbosity
239    call msg_setVerbThreshold(msg_NML, beSilent_opt=.true.)
240    msg_arrayVertical = arrayVertical
241
242  end subroutine msg_readNml
243
244  !--------------------------------------------------------------------------
245  ! msg_write (private)
246  !--------------------------------------------------------------------------
247  subroutine msg_write(origin, message, separator_opt)
248    !
249    !:Purpose: Format and write message to default output
250    !
251    implicit none
252  
253    ! Arguments:
254    character(len=*),           intent(in) :: origin        ! originating subroutine/function
255    character(len=*),           intent(in) :: message       ! message to be printed
256    character(len=*), optional, intent(in) :: separator_opt ! separator string between origin and message
257  
258    ! Locals:
259    integer :: originLen, oneLineMsgLen, posIdx
260    character(len=15) :: firstLineFormat, otherLineFormat
261    character(len=msg_lineLen)  :: msgLine
262    character(len=msg_lineLen)  :: readLine
263    character(len=:), allocatable :: originTrunc, adjustedLine, separator
264
265    if (present(separator_opt)) then
266      separator = separator_opt
267    else
268      separator = ': '
269    end if
270
271    if (len(origin) > msg_lineLen) then
272      originTrunc = origin(1:msg_lineLen)
273    else
274      originTrunc = origin
275    end if
276    originLen = len(originTrunc)
277
278    oneLineMsgLen = msg_lineLen - originLen - len(separator)
279  
280    if (len(message) > oneLineMsgLen) then
281      ! Multiple lines message
282      ! format: "origin: message on the first line..........."
283      !         "        second line........................."
284      !         "        last line"
285      posIdx = 0
286      readLine = message(1:oneLineMsgLen+1)
287      msgLine = msg_breakOnSpace(readLine)
288      posIdx = posIdx + len(trim(msgLine)) +1
289      write(firstLineFormat,'(A,I2,A,I2,A,I2,A)') '(A',originLen,',A',len(separator),&
290                                                  ',A', len(trim(msgLine)),')'
291      write(*,firstLineFormat) originTrunc, separator, message(1:oneLineMsgLen)
292      oneLineMsgLen = msg_lineLen - msg_indent - len(separator)
293      do
294        if ( posIdx >= len(message) ) then
295          ! message printed
296          return
297        else if ( posIdx + oneLineMsgLen > len(trim(message)) ) then
298          ! last line
299          msgLine = message(posIdx+1:len(message))
300        else
301          ! neither first nor last
302          readLine = message(posIdx+1:min(posIdx+oneLineMsgLen+1,len(message)))
303          msgLine = msg_breakOnSpace(readLine)
304        end if
305        adjustedLine = adjustl(trim(msgLine))
306        posIdx = posIdx + len(adjustedLine) +1
307        write(otherLineFormat,'(A,I2,A,I2,A)') '(A',msg_indent,',A', &
308                                                len(adjustedLine),')'
309        write(*,otherLineFormat) repeat(' ',msg_indent),adjustedLine
310      end do
311    else
312      ! Single lines message
313      ! format: "origin: short message"
314    write(firstLineFormat,'(A,I2,A,I2,A, I2, A)') '(A',originLen,',A',len(separator), &
315                                                  ',A',len(message),')'
316      write(*,firstLineFormat) originTrunc, separator, message
317    end if
318    contains
319      !----------------------------------------------------------------------
320      ! msg_breakOnSpace (private)
321      !----------------------------------------------------------------------
322      function msg_breakOnSpace(line) result(shorterLine)
323        !
324        !:Purpose: Breaks line on last full word
325        !
326        implicit none
327    
328        ! Arguments:
329        character(len=*), intent(inout) :: line        ! line of text to be processed
330        ! Result:
331        character(len=msg_lineLen)      :: shorterLine ! resulting processed line of text
332
333        ! Locals:
334        integer :: idx, idxNewLine
335    
336        idxNewLine = scan(line, new_line(''))
337        if ( idxNewLine == 0) then ! no new line
338          idx = scan(trim(line),' ',back=.true.)
339          if (idx == 0 .or. idx == len(trim(line)) ) then
340            shorterLine = trim(line)
341          else
342            shorterLine = line(1:idx-1)
343          end if
344        else ! presence of a new line
345          shorterLine = trim(line(1:idxNewLine-1))
346        end if
347    
348      end function msg_breakOnSpace
349  end subroutine msg_write
350
351  !--------------------------------------------------------------------------
352  ! msg_str2str (private)
353  !--------------------------------------------------------------------------
354  function msg_str2str(stringIn, trim_opt, quote_opt) result(string)
355    !
356    !:Purpose: Trivial overloading (for uniformity of concatenation)
357    !
358    implicit none
359
360    ! Arguments:
361    character(len=*),  intent(in)  :: stringIn  ! input string to be processed
362    logical, optional, intent(in)  :: trim_opt  ! choose to trim the string (possibly inside quotes)
363    logical, optional, intent(in)  :: quote_opt ! choose to write preceding and following single quote "'" to insist it is a string
364    ! Result:
365    character(len=:), allocatable :: string    ! resulting string that was processed
366
367    ! Locals:
368    logical :: doTrim, showQuotes
369    character(len=1)  :: quote
370
371    if ( present(trim_opt) ) then
372      doTrim = trim_opt
373    else
374      doTrim = .true.
375    end if
376    if ( present(quote_opt) ) then
377      showQuotes = quote_opt
378    else
379      showQuotes = .true.
380    end if
381    if (showQuotes) then
382      quote = "'"
383    else
384      quote = ''
385    end if
386
387    if (doTrim) then
388      string = quote//trim(stringIn)//quote
389    else
390      string = quote//stringIn//quote
391    end if
392
393  end function msg_str2str
394
395  !--------------------------------------------------------------------------
396  ! msg_log2str (private)
397  !--------------------------------------------------------------------------
398  function msg_log2str(num) result(string)
399    !
400    !:Purpose: Returns string representation of `logical` variable value
401    !
402    implicit none
403
404    ! Arguments:
405    logical,                      intent(in)  :: num    ! input logical variable to be interpreted
406    ! Result:
407    character(len=:), allocatable             :: string ! resulting string with logical value
408
409    ! Locals:
410    character(len=msg_num2strBufferLen) :: buffer
411
412    write(buffer,*) num
413    string = trim(adjustl(buffer))
414
415  end function msg_log2str
416
417  !--------------------------------------------------------------------------
418  ! msg_int2str (private)
419  !--------------------------------------------------------------------------
420  function msg_int2str(num) result(string)
421    !
422    !:Purpose: Returns string representation of `integer`
423    !
424    implicit none
425
426    ! Arguments:
427    integer,                      intent(in)  :: num    ! input integer variable to be interpreted
428    ! Result:
429    character(len=:), allocatable             :: string ! resulting string with integer value
430
431    ! Locals:
432    character(len=msg_num2strBufferLen) :: buffer
433
434    write(buffer,*) num
435    string = trim(adjustl(buffer))
436
437  end function msg_int2str
438
439  !--------------------------------------------------------------------------
440  ! msg_real42str (private)
441  !--------------------------------------------------------------------------
442  function msg_real42str(num, digits_opt) result(string)
443    !
444    !:Purpose: Returns string representation of `real(4)`
445    !
446    implicit none
447
448    ! Arguments:
449    real(4),                      intent(in) :: num        ! input real(4) variable to be interpreted
450    integer, optional,            intent(in) :: digits_opt ! number of digits to include in output
451    ! Result:
452    character(len=:), allocatable            :: string     ! resulting string with variable's value
453
454    ! Locals:
455    character(len=20)                   :: readFmt, digitBuffer
456    character(len=msg_num2strBufferLen) :: buffer
457
458    if (present(digits_opt)) then
459      write(digitBuffer, *) digits_opt
460      write(readFmt,*) '(F20.'//trim(adjustl(digitBuffer))//')' 
461      write(buffer, readFmt) num
462    else
463      write(buffer,*) num
464    end if
465    string = trim(adjustl(buffer))
466
467  end function msg_real42str
468
469  !--------------------------------------------------------------------------
470  ! msg_real82str (private)
471  !--------------------------------------------------------------------------
472  function msg_real82str(num, digits_opt) result(string)
473    !
474    !:Purpose: Returns string representation of `real(8)` 
475    !
476    implicit none
477
478    ! Arguments:
479    real(8),                      intent(in) :: num        ! input real(8) variable to be interpreted
480    integer, optional,            intent(in) :: digits_opt ! number of digits to include in output
481    ! Result:
482    character(len=:), allocatable            :: string     ! resulting string with variable's value
483
484    ! Locals:
485    character(len=20)                   :: readFmt, digitBuffer
486    character(len=msg_num2strBufferLen) :: buffer
487
488    if (present(digits_opt)) then
489      write(digitBuffer, *) digits_opt
490      write(readFmt,*) '(F20.'//trim(adjustl(digitBuffer))//')' 
491      write(buffer, readFmt) num
492    else
493      write(buffer,*) num
494    end if
495    string = trim(adjustl(buffer))
496
497  end function msg_real82str
498
499  !--------------------------------------------------------------------------
500  ! msg_charArray2str (private)
501  !--------------------------------------------------------------------------
502  function msg_charArray2str(array, vertical_opt) result(string)
503    !
504    !:Purpose: Returns string representation of `character(len=*), dimension(:)` 
505    !
506    implicit none
507
508    ! Arguments:
509    character(len=*),             intent(in) :: array(:)     ! input character string array
510    logical, optional,            intent(in) :: vertical_opt ! choose to represent array vertically (default .false.)
511    ! Result:
512    character(len=:), allocatable            :: string       ! resulting string
513
514    ! Locals:
515    integer           :: arrIndex
516    logical           :: vertical
517    character(len=:), allocatable  :: sep
518
519    if (present(vertical_opt)) then
520      vertical = vertical_opt
521    else
522      vertical = msg_arrayVertical
523    end if
524
525    if (vertical) then
526      sep = new_line('')//repeat(' ', msg_indent)
527      string = '(/'//sep
528    else
529      sep = ', '
530      string = '(/ '
531    end if
532
533    do arrIndex = 1, size(array)
534      string = string//trim(array(arrIndex))
535      if (arrIndex /= size(array)) string = string//sep
536    end do
537    string = string//' /)'
538
539  end function msg_charArray2str
540
541  !--------------------------------------------------------------------------
542  ! msg_logArray2str (private)
543  !--------------------------------------------------------------------------
544  function msg_logArray2str(array, vertical_opt) result(string)
545    !
546    !:Purpose: Returns string representation of `logical, dimension(:)` 
547    !
548    implicit none
549
550    ! Arguments:
551    logical,           intent(in) :: array(:)     ! input array of logical variables
552    logical, optional, intent(in) :: vertical_opt ! choose to represent the array vertically (default .false.)
553    ! Result:
554    character(len=:), allocatable :: string       ! resulting string
555
556    ! Locals:
557    integer           :: arrIndex
558    logical           :: vertical
559    character(len=:), allocatable  :: sep
560
561    if (present(vertical_opt)) then
562      vertical = vertical_opt
563    else
564      vertical = msg_arrayVertical
565    end if
566
567    if (vertical) then
568      sep = new_line('')//repeat(' ', msg_indent)
569      string = '(/'//sep
570    else
571      sep = ', '
572      string = '(/ '
573    end if
574
575    do arrIndex = 1, size(array)
576      string = string//msg_log2str(array(arrIndex))
577      if (arrIndex /= size(array)) string = string//sep
578    end do
579    string = string//' /)'
580    
581  end function msg_logArray2str
582
583  !--------------------------------------------------------------------------
584  ! msg_intArray2str (private)
585  !--------------------------------------------------------------------------
586  function msg_intArray2str(array, vertical_opt) result(string)
587    !
588    !:Purpose: Returns string representation of `integer, dimension(:)` 
589    !
590    implicit none
591
592    ! Arguments:
593    integer,           intent(in) :: array(:)     ! input array of integer variables
594    logical, optional, intent(in) :: vertical_opt ! choose to represent the array vertically (defaults .false.)
595    ! Result:
596    character(len=:), allocatable :: string       ! resulting string
597
598    ! Locals:
599    integer           :: arrIndex
600    logical           :: vertical
601    character(len=:), allocatable  :: sep
602
603    if (present(vertical_opt)) then
604      vertical = vertical_opt
605    else
606      vertical = msg_arrayVertical
607    end if
608
609    if (vertical) then
610      sep = new_line('')//repeat(' ', msg_indent)
611      string = '(/'//sep
612    else
613      sep = ', '
614      string = '(/ '
615    end if
616
617    do arrIndex = 1, size(array)
618      string = string//msg_int2str(array(arrIndex))
619      if (arrIndex /= size(array)) string = string//sep
620    end do
621    string = string//' /)'
622    
623  end function msg_intArray2str
624
625  !--------------------------------------------------------------------------
626  ! msg_real4Array2str (private)
627  !--------------------------------------------------------------------------
628  function msg_real4Array2str(array, digits_opt, vertical_opt) result(string)
629    !
630    !:Purpose: Returns string representation of `real(4), dimension(:)` 
631    !
632    implicit none
633
634    ! Arguments:
635    real(4),           intent(in) :: array(:)     ! input array of real(4) variables
636    integer, optional, intent(in) :: digits_opt   ! number of digits to include in output 
637    logical, optional, intent(in) :: vertical_opt ! choose to represent array vertically (default .false.)
638    ! Result:
639    character(len=:), allocatable :: string       ! resulting string
640
641    ! Locals:
642    integer           :: arrIndex
643    logical           :: vertical
644    character(len=:), allocatable  :: sep
645
646    if (present(vertical_opt)) then
647      vertical = vertical_opt
648    else
649      vertical = msg_arrayVertical
650    end if
651
652    if (vertical) then
653      sep = new_line('')//repeat(' ', msg_indent)
654      string = '(/'//sep
655    else
656      sep = ', '
657      string = '(/ '
658    end if
659
660    do arrIndex = 1, size(array)
661      string = string//msg_real42str(array(arrIndex), digits_opt=digits_opt)
662      if (arrIndex /= size(array)) string = string//sep
663    end do
664    string = string//' /)'
665    
666  end function msg_real4Array2str
667
668  !--------------------------------------------------------------------------
669  ! msg_real8Array2str (private)
670  !--------------------------------------------------------------------------
671  function msg_real8Array2str(array, digits_opt, vertical_opt) result(string)
672    !
673    !:Purpose: Returns string representation of `real(8), dimension(:)` 
674    !
675    implicit none
676
677    ! Arguments:
678    real(8),           intent(in) :: array(:)     ! input array of real(4) variables
679    integer, optional, intent(in) :: digits_opt   ! number of digits to include in output
680    logical, optional, intent(in) :: vertical_opt ! choose to represent array vertically (default .false.)
681    ! Result:
682    character(len=:), allocatable :: string       ! resulting string
683
684    ! Locals:
685    integer           :: arrIndex
686    logical           :: vertical
687    character(len=:), allocatable  :: sep
688
689    if (present(vertical_opt)) then
690      vertical = vertical_opt
691    else
692      vertical = msg_arrayVertical
693    end if
694
695    if (vertical) then
696      sep = new_line('')//repeat(' ', msg_indent)
697      string = '(/'//sep
698    else
699      sep = ', '
700      string = '(/ '
701    end if
702
703    do arrIndex = 1, size(array)
704      string = string//msg_real82str(array(arrIndex), digits_opt=digits_opt)
705      if (arrIndex /= size(array)) string = string//sep
706    end do
707    string = string//' /)'
708    
709  end function msg_real8Array2str
710
711end module message_mod