source: trunk/libIGCM/libIGCM_debug/libIGCM_debug.ksh @ 1280

Last change on this file since 1280 was 1280, checked in by sdipsl, 8 years ago
  • enhance the log message when computing job is stopped by a post processing job. The prost processing job name thjat has trigger the fatal will be written next the Fatal keyword in the run.card
  • Property licence set to
    The following licence information concerns ONLY the libIGCM tools
    ==================================================================

    Copyright © Centre National de la Recherche Scientifique CNRS
    Commissariat à l'Énergie Atomique CEA

    libIGCM : Library for Portable Models Computation of IGCM Group.

    IGCM Group is the french IPSL Global Climate Model Group.

    This library is a set of shell scripts and functions whose purpose is
    the management of the initialization, the launch, the transfer of
    output files, the post-processing and the monitoring of datas produce
    by any numerical program on any plateforme.

    This software is governed by the CeCILL license under French law and
    abiding by the rules of distribution of free software. You can use,
    modify and/ or redistribute the software under the terms of the CeCILL
    license as circulated by CEA, CNRS and INRIA at the following URL
    "http://www.cecill.info".

    As a counterpart to the access to the source code and rights to copy,
    modify and redistribute granted by the license, users are provided only
    with a limited warranty and the software's author, the holder of the
    economic rights, and the successive licensors have only limited
    liability.

    In this respect, the user's attention is drawn to the risks associated
    with loading, using, modifying and/or developing or reproducing the
    software by the user in light of its specific status of free software,
    that may mean that it is complicated to manipulate, and that also
    therefore means that it is reserved for developers and experienced
    professionals having in-depth computer knowledge. Users are therefore
    encouraged to load and test the software's suitability as regards their
    requirements in conditions enabling the security of their systems and/or
    data to be ensured and, more generally, to use and operate it in the
    same conditions as regards security.

    The fact that you are presently reading this means that you have had
    knowledge of the CeCILL license and that you accept its terms.
  • Property svn:keywords set to Revision Author Date
File size: 36.0 KB
RevLine 
[913]1#!/bin/ksh
[2]2
3#**************************************************************
4# Author: Patrick Brockmann, Martial Mancip
[373]5# Contact: Patrick.Brockmann__at__cea.fr Martial.Mancip__at__ipsl.jussieu.fr
6# $Revision::                                          $ Revision of last commit
7# $Author::                                            $ Author of last commit
8# $Date::                                              $ Date of last commit
[2]9# IPSL (2006)
10#  This software is governed by the CeCILL licence see libIGCM/libIGCM_CeCILL.LIC
11#
12#**************************************************************
13
14#==================================================
15# The documentation of this file can be automatically generated
[913]16# if you use the prefix #D- for comments to be extracted.
[2]17# Extract with command: cat lib* | grep "^#D-" | cut -c "4-"
18#==================================================
19
20#==================================================
21# Add high level verbosity
22typeset -i Verbosity=${Verbosity:=3}
23
24#==================================================
25# DEBUG_debug
26# Add low level verbosity
[1083]27DEBUG_debug=${DEBUG_debug:=false}
[2]28
[872]29#==================================================
30# GENERATE RANDOM ERROR ; only apply if ( ${DEBUG_debug} )
[1244]31typeset -r RandomError=false
[872]32
[2]33#==================================================
34# NULL_STR
35# Default null string
[913]36typeset -r NULL_STR="_0_"
[2]37
38#==================================================
39# libIGCM_CurrentTag
40# Current libIGCM tag, check compatibilty with *.card
[1244]41typeset -r libIGCMVersion="2.7"
[2]42
43#==================================================
44# Exit Flag (internal debug)
45# When true, end the master loop AFTER SAVES FILES
46ExitFlag=false
47
48#==================================================
[1243]49# When we start to run the simulation is not finished
50simulationIsOver=false
51
52#==================================================
[1268]53# When we start to run we dont flush AMQP messages
54FlushAMQP=false
55
56#==================================================
[2]57# Declare a stack of functions calls
[59]58unset IGCM_debug_Stack
59unset IGCM_debug_StackArgs
[913]60unset IGCM_debug_StackTiming
[54]61IGCM_debug_Stack[0]=${NULL_STR}
62IGCM_debug_StackArgs[0]=${NULL_STR}
[913]63IGCM_debug_StackTiming[0]=${NULL_STR}
[2]64IGCM_debug_LenStack=0
65
66#D-#==================================================================
[913]67#D-function IGCM_debug_getDate_ms
68#D- * Purpose: Give number of milliseconds since 01-jan-1970
69function IGCM_debug_getDate_ms
70{
[926]71  typeset nanosecs ms
[913]72  # nano secondes since 01-jan-1970
73  nanosecs=$( date +%s%N )
74
75  # truncate the last 6 digits to get milliseconds since 01-jan-1970
76  ms=${nanosecs:0:${#nanosecs}-6}
77
78  echo "$ms"
79}
80
81#D-#==================================================================
82#D-function IGCM_debug_sizeOfTabContent
83#D- * Purpose: Give sumed size of a list of files
[924]84#D- * Usage: IGCM_debug_sizeOfTabContent entityList destination
85#D- *        where entityList is a list of files or directory
86#D- *        where dest is either a directory or a file name
[913]87function IGCM_debug_sizeOfTabContent
88{
[924]89  typeset entityListe destination iEntity sizeKo sumSizeKo sumSizeMo
90
91  eval set +A entityListe \${${1}}
[941]92  destination=${2}
[924]93  sumSizeKo=0
94
95  # Here we will try to compute size (file or directory size) from local path and not from archive.
[941]96  for ((i = 0; i < ${#entityListe[*]}; i += 1)) ; do
97    if [ -f ${entityListe[$i]} ] ; then
98      # One file or a bunch of files has been copied without renaming from a visible filesystem
99      iEntity=${entityListe[$i]}
100    elif [ -f ${entityListe[$i]##/*/} ] ; then
101      # One file or a bunch of files has been copied without renaming from an non visible filesystem
[924]102      # remove path /home/login/../ from entityListe elements
103      iEntity=${entityListe[$i]##/*/}
104    elif [ -f ${destination} ] ; then
[941]105      # a file has been copied and renamed
[924]106      iEntity=${destination}
107    elif [ -f ${destination}/${entityListe[$i]##/*/} ] ; then
108      # a copy in a directory but not in ${PWD}
109      iEntity=${destination}/${entityListe[$i]##/*/}
110    elif [ -d ${entityListe[$i]} ] ; then
[941]111      # a directory has been copied from a non remote place
[924]112      iEntity=${entityListe[$i]}
113    elif [ -d ${destination}/${entityListe[$i]##/*/} ] ; then
[941]114      # a directory has been copied from a remote archive and not renamed
[924]115      iEntity=${destination}/${entityListe[$i]##/*/}
116    elif [ -d ${destination} ] ; then
[941]117      # a directory has been copied from a remote archive and renamed
[924]118      iEntity=${destination}
[917]119    fi
[1083]120    sizeKo=$( du --apparent-size -skL ${iEntity} | gawk '{print $1}' )
[924]121    sumSizeKo=$(( $sumSizeKo + $sizeKo ))
[913]122  done
[924]123  sumSizeMo=$( echo "scale=6;${sumSizeKo}/1024" | bc )
124  echo "${sumSizeKo}|${sumSizeMo}"
[913]125}
126
127#D-#==================================================================
[983]128#D-function IGCM_debug_send_AMQP_msg__MAILTUNNEL
129#D- * Purpose: Take over AMQP C client using mail as a message recipient
130#D- * One argument : base64 encoded message
[1051]131#D- * Attach encoded config.card when starting the simulation
132
[983]133function IGCM_debug_send_AMQP_msg__MAILTUNNEL {
134
[987]135  typeset b64_encoded_msg mail_recipient
[1076]136  typeset buffer send_messages mail_frequency
[987]137  typeset last_mail_date__file
[1234]138  typeset secondsBetweenRefAndLastMail secondsSinceLastMail
[987]139
[983]140  b64_encoded_msg=$1
141
[1072]142  mail_recipient="superviseur@ipsl.jussieu.fr"
[983]143  send_messages=0
144  mail_frequency=3600 # in seconds
145  # use to keep track when was last mail sent (maybe to be replaced with global variable)
[1150]146  last_mail_date__file=${R_BUF}/.stamp.${config_UserChoices_TagName}.${config_UserChoices_JobName}
147  # use to accumulate messages before sending them
148  buffer=${R_BUF}/.buffer.${config_UserChoices_TagName}.${config_UserChoices_JobName}
[983]149
150  # init
151  if [ ! -f "${buffer}" ]; then
[1150]152    touch ${buffer}
[983]153  fi
154
155  if [ ! -f "${last_mail_date__file}" ]; then
[1150]156    touch ${last_mail_date__file}
[983]157  else
[1150]158    # compute last time the file was changed (in seconds)
[1234]159    secondsBetweenRefAndLastMail=$(stat -c %Y ${last_mail_date__file})
160    status=$?
161    #
[1275]162    # Only execute this block when the stat command succeeded.
163    # The stat command might fail in some circumstance but we consider it is ok to continue anyway.
164    if [ ${status} -eq 0 ] ; then
165      secondsSinceLastMail=$(( $(date +%s) - ${secondsBetweenRefAndLastMail} ))
166      # send message when exceeding threshold
167      [ ${secondsSinceLastMail} -gt ${mail_frequency} ] && send_messages=1
[1234]168    fi
[983]169  fi
170
[997]171  # queue messages in the buffer
172  echo ${b64_encoded_msg} >> ${buffer}
173
174  # send mail
[1051]175
[1053]176  if [ X${initBigBro} = Xtrue ] ; then
[1087]177    #echo $(date +"%Y-%m-%dT%H:%M:%S.%N%z") > ${SUBMIT_DIR}/mail.txt
[1051]178    mailx -s "[TEMPORARY AMQP CHANNEL]" -a ${SUBMIT_DIR}/config.card.base64 ${mail_recipient} < ${buffer} # send buffer
179    rm -f $buffer ; touch ${buffer}                                    # clear buffer
180    touch ${last_mail_date__file}                                      # memorize last mail date
181    initBigBro=false
[1150]182  elif [ ${send_messages} -eq 1 ] ; then
[1087]183    #echo $(date +"%Y-%m-%dT%H:%M:%S.%N%z") >> ${SUBMIT_DIR}/mail.txt
[1051]184    mailx -s "[TEMPORARY AMQP CHANNEL]" ${mail_recipient}  < ${buffer} # send buffer
[1150]185    rm -f ${buffer} ; touch ${buffer}                                  # flush the buffer
[1051]186    touch ${last_mail_date__file}                                      # memorize last mail date
[983]187  fi
[987]188
[1189]189  if ( ${FlushAMQP} ) ; then
[1150]190    mailx -s "[TEMPORARY AMQP CHANNEL]" ${mail_recipient}  < ${buffer} # send buffer
191    rm -f ${buffer}                                                    # cleaning behind us
192    rm -f ${last_mail_date__file}                                      # cleaning behind us
193  fi
194
[983]195  # Allways all good for now.
196  return 0
197}
198
199#D-#==================================================================
[1162]200#D-function IGCM_debug_sendAMQP_Metrics
201#D- * Purpose: Take over AMQP C client using mail as a message recipient
[1202]202#D- * Two arguments : - Directory where metrics.json files can be found
203#D- *                 - Metrics Group Name. metrics will be added to this group
[1162]204#D- * Attach encoded metrics.json files.
205
206function IGCM_debug_sendAMQP_Metrics {
207
208  typeset mail_recipient encodedBody
209  if [ X${ActivateBigBro} = Xtrue ] ; then
210    mail_recipient="superviseur@ipsl.jussieu.fr"
211    # Metrics tag on server side
212    code=7100
213    # Usual AMQP message to route messages on server side
[1202]214    encodedBody=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"msgUID\":\"$(uuidgen)\",\"metricsGroupName\":\"${2}\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" |  base64 -w 0 )
[1162]215    # send mail
216    attachmentsOptions=""
217    for metricsFile in $( ls $1/*json ) ; do
218      attachmentsOptions="-a ${metricsFile} ${attachmentsOptions}"
219    done
[1204]220    IGCM_debug_Print 2 "IGCM_debug_sendAMQP_Metrics "
[1162]221    echo ${encodedBody}|mailx -s "[TEMPORARY AMQP CHANNEL]" ${attachmentsOptions} ${mail_recipient}
222  fi
223
224  # Allways all good for now.
225  return 0
226}
227
228#D-#==================================================================
[913]229#D-function IGCM_debug_SendAMQP
230#D- * Purpose: Send body; encoded body and config.card to rabbitMQ
[1053]231function IGCM_debug_sendAMQP {
232
[913]233  typeset decal first additionnalOption encodedBody
234
235  # Encode message Body
236  encodedBody=$( echo "${Body}" | base64 -w 0 )
237
238  # Send config.card ?
239  if [ X${1} = Xactivate ] ; then
240    # Encode config.card
241    cat ${SUBMIT_DIR}/config.card | base64 -w 0 > ${SUBMIT_DIR}/config.card.base64
242    # Prepare additionnal option
243    additionnalOption="-f ${SUBMIT_DIR}/config.card.base64"
[1076]244    #
[1051]245    initBigBro=true
[913]246  else
247    additionnalOption=
[1051]248    #
249    initBigBro=false
[913]250  fi
251
252  # Only cosmetics : stack file
[1115]253  if [ X${ActivateStackFilling} = Xtrue ] ; then
254    decal=0
255    while [ ${decal} -lt ${IGCM_debug_LenStack} ]; do
256      printf ' ' >> ${StackFileLocation}/${StackFileName}
257      (( decal = decal + 1 ))
258    done
259    # Log to stack file using human readable format
260    echo "${Body}" >> ${StackFileLocation}/${StackFileName}
261  fi
[913]262
[983]263  # Log separately encoded AMQP message command for reuse in a mock up
[1120]264  #echo sendAMQPMsg -h localhost -p 5672 ${additionnalOption} -b ${encodedBody} >> ${RUN_DIR_PATH}/send.AMQP.${config_UserChoices_JobName}.${config_UserChoices_ExperimentName}.${config_UserChoices_SpaceName}.${config_UserChoices_TagName}.${CumulPeriod}.history.txt
[913]265
266  # Send the message
[983]267  if [ X${BigBrotherChannel} = XMAIL ] ; then
268    IGCM_debug_send_AMQP_msg__MAILTUNNEL "${encodedBody}"
269    status=$?
270  else
271    sendAMQPMsg -h localhost -p 5672 ${additionnalOption} -b ${encodedBody}
272    status=$?
273  fi
274
[913]275  if [ ${status} -gt 0 ] ; then
276    IGCM_debug_Print 2 "IGCM_debug_Push/PopStack/ActivateBigBro : command sendAMQPMsg failed error code ${status}"
[1051]277    echo sendAMQPMsg -h localhost -p 5672 -b "${Body}"
[1090]278    exit 1
[913]279  fi
280}
281
282#D-#==================================================================
[2]283#D-function IGCM_debug_CallStack
[913]284#D-* Purpose: Print the call stack tree from the oldest to the youngest (opposite of the display standard)
[2]285#D-
286function IGCM_debug_CallStack {
[544]287  if ( $DEBUG_debug ) ; then
[913]288    # Cosmetics
[544]289    typeset i decal
[823]290    i=0
[544]291    until [ $i -eq ${IGCM_debug_LenStack} ]; do
292      decal=0
293      until [ $decal -eq ${i} ]; do
[869]294        printf -- ' '
[823]295        (( decal = decal + 1 ))
[544]296      done
[869]297      echo "$i - ${IGCM_debug_Stack[$(( $IGCM_debug_LenStack-$i-1 ))]}" "(${IGCM_debug_StackArgs[$(( $IGCM_debug_LenStack-$i-1 ))]})"
[823]298      ((i = i + 1))
[544]299    done
300  fi
[2]301}
302
303#D-#==================================================================
304#D-function IGCM_debug_PushStack
305#D-* Purpose: Push a function name in the stack
306#D-
307function IGCM_debug_PushStack {
[544]308  if ( $DEBUG_debug ) ; then
[913]309    typeset decal inputs startTime_ms
310
311    # Only cosmetics : stack file
[1115]312    if [ X${ActivateStackFilling} = Xtrue ] ; then
313      echo >> ${StackFileLocation}/${StackFileName}
314      decal=0
315      while [ ${decal} -lt ${IGCM_debug_LenStack} ]; do
316        printf ' ' >> ${StackFileLocation}/${StackFileName}
317        (( decal = decal + 1 ))
318      done
[2]319
[1115]320      # Fill the stack file
321      echo "> ${IGCM_debug_LenStack} : ${@}" >> ${StackFileLocation}/${StackFileName}
322    fi
[926]323
[913]324    # Save input list in an indexed array
[823]325    INPUTS=( $@ )
[913]326
327    # Get timing information
328    startTime_ms=$( IGCM_debug_getDate_ms )
329
[544]330    # We add function call name on beginning of the stack
331    set +A IGCM_debug_Stack -- ${1} ${IGCM_debug_Stack[*]}
[2]332
[913]333    # Save timing in milliseconds in an indexed array
334    set +A IGCM_debug_StackTiming -- ${startTime_ms} ${IGCM_debug_StackTiming[*]}
335
[544]336    # We include the "null" Args in the beginning of the StackArgs
[913]337    set +A IGCM_debug_StackArgs ${NULL_STR} ${IGCM_debug_StackArgs[*]}
338
[544]339    # Then, we shift StackArgs tabular
[1065]340    # Replacing blank separated list by comma separated list of quoted elements (except the first and last element)
[913]341    if [ $# -gt 1 ]; then
[1065]342      IGCM_debug_StackArgs[0]=$(echo ${INPUTS[*]:1} | sed -e "s/\ /\",\"/g" )
[544]343    fi
[855]344
345    # Increment LenStack
[544]346    (( IGCM_debug_LenStack = IGCM_debug_LenStack + 1 ))
[2]347
[869]348    #IGCM_debug_CallStack
[544]349  fi
[2]350}
351
352#D-#==================================================================
353#D-function IGCM_debug_PopStack
354#D-* Purpose: Pop a function name in the stack
355#D-
356function IGCM_debug_PopStack {
[544]357  if ( $DEBUG_debug ) ; then
[926]358    typeset i decal command arguments startTime_ms endTime_ms
[941]359    typeset instrumentation dest prefix
[926]360    # they are not typeset because they are send "by adress" to son functions
361    # we unset them to avoid "memory effect"
362    unset fileList source
[913]363
364    # INTRODUCE SIMPLE ERROR GENERATOR TO TEST SUPERVISOR
365    # PROBABILITY ERROR IS 0.0001 PER COMMAND OR FUNCTION CALL
366    # THERE ARE ~500 COMMAND OR FUNCTION CALL PER PERIOD
[1277]367    # ONLY WHEN TaskType is "computing".
368    if [ X${ActivateBigBro} = Xtrue ] ; then
369      if [ X${TaskType} = Xcomputing ]; then
370        if ( ${RandomError} ) ; then
371          if [ $((RANDOM%10000)) -le 10 ] ; then
372            IGCM_debug_Print 1 "Random error has been triggered"
373            if [ X${ActivateStackFilling} = Xtrue ] ; then
374              echo "RANDOM ERROR" >> ${StackFileLocation}/${StackFileName}
375            fi
376            ExitFlag=true
377          fi
[1115]378        fi
[913]379      fi
380    fi
381
[544]382    if [ "${IGCM_debug_Stack[0]}" = "${1}" ]; then
[913]383      # Everything is cool
384
385      # Get timing information
386      endTime_ms=$( IGCM_debug_getDate_ms )
387
388      # Save Stack information before poping the stack
389      command=${IGCM_debug_Stack[0]}
390
[1084]391      # Go from comma separated list of quoted elements (except the first and the last element)
392      # to unquoted space separated elements in an array
[1083]393      set -A arguments -- $( echo ${IGCM_debug_StackArgs[0]} | sed -e "s/\",\"/\ /g" )
[913]394
395      # Save Stack information before poping the stack
396      startTime_ms=${IGCM_debug_StackTiming[0]}
397
398      # Pop the stack
[823]399      (( IGCM_debug_LenStack = IGCM_debug_LenStack - 1 ))
400      set -A IGCM_debug_Stack -- ${IGCM_debug_Stack[*]:1}
401      set -A IGCM_debug_StackArgs -- ${IGCM_debug_StackArgs[*]:1}
[913]402      set -A IGCM_debug_StackTiming -- ${IGCM_debug_StackTiming[*]:1}
[544]403    else
404      echo 'IGCM_debug_Exit : stack is corrupted ! LenStack =' ${IGCM_debug_LenStack}
405      IGCM_debug_Exit $@
406    fi
[913]407
[914]408    # Special actions depending on command to prepare IGCM_debug_PrintInfosActions call
[913]409    # We are interested in:
410    #  0. Which command performs the work
411    #  1. Size of entity we are working with
412    #  2. Where are we reading
413    #  3. Where are we writing
414    #  4. How long it took
415
[915]416    instrumentation=false
417
[913]418    case ${command} in
[925]419    # Classical copy (only files are given to IGCM_sys_Cp as options)
420    IGCM_sys_Cp)
421      instrumentation=true
422      # All but the latest
423      fileList=${arguments[*]:0:${#arguments[*]}-1}
424      # just need the first file to get the directory
425      source=${arguments[0]}
426      # Nothing but the latest
427      dest=${arguments[${#arguments[*]}-1]}
428      # Size of file whose name are stored in a list
429      entitySize=$( IGCM_debug_sizeOfTabContent fileList ${dest} )
430      ;;
431
[913]432    # Copy from archive machine or from buffer
433    IGCM_sys_Get|IGCM_sys_GetBuffer)
[915]434      instrumentation=true
[913]435      if [ ${#arguments[*]} -eq 2 ] ; then
436        source=${arguments[0]}
437        dest=${arguments[1]}
438        # Size of file whose name are stored in a variable
[917]439        entitySize=$( IGCM_debug_sizeOfTabContent source ${dest} )
[913]440      elif ( [ ${#arguments[*]} -eq 3 ] && [ ${arguments[0]} = '/l' ] ) ; then
[936]441        # IGCM_sys_Get /l liste_file[*] /ccc/scratch/cont003/dsm/p86denv/RUN_DIR/985998_14754/
[913]442        # Keep the array name hosting the all list
[936]443        eval set +A fileList \${${arguments[1]}}
[913]444        # just need the first file to get the directory
[936]445        source=${fileList[0]}
[931]446        dest=${arguments[2]}
[934]447        # Size of file whose name are stored in a list
[936]448        entitySize=$( IGCM_debug_sizeOfTabContent fileList[*] ${dest} )
[913]449      elif [ [ ${#arguments[*]} -ge 3 ] ; then
450       # All but the latest
[916]451        fileList=${arguments[*]:0:${#arguments[*]}-1}
[913]452        # just need the first file to get the directory
453        source=${arguments[0]}
454        # Nothing but the latest
455        dest=${arguments[${#arguments[*]}-1]}
456        # Size of file whose name are stored in a list
[917]457        entitySize=$( IGCM_debug_sizeOfTabContent fileList ${dest} )
[913]458      fi
459      ;;
460
[925]461    # Copy from compute node or copy to archive/buffer
462    IGCM_sys_Get_Master|IGCM_sys_Get_Dir|IGCM_sys_Put_Out|IGCM_sys_PutBuffer_Out)
[924]463      instrumentation=true
[916]464      source=${arguments[0]}
[924]465      dest=${arguments[1]}
466      # Size of file whose name are stored in a variable
467      entitySize=$( IGCM_debug_sizeOfTabContent source ${dest} )
[913]468      ;;
469
470    # Rebuild command
471    IGCM_sys_rebuild|IGCM_sys_rebuild_station)
[915]472      instrumentation=true
[913]473      # All but the first
474      fileList=${arguments[*]:1:${#arguments[*]}-1}
475      # just need a file to get the directory
476      source=${arguments[1]}
477      # Nothing but the first
478      dest=${arguments[0]}
479      # Size of file whose name are stored in a list
[917]480      entitySize=$( IGCM_debug_sizeOfTabContent fileList ${dest} )
[913]481      ;;
[941]482
[926]483    # NCO commands
484    IGCM_sys_ncrcat|IGCM_sys_ncecat|IGCM_sys_ncra|IGCM_sys_ncks|IGCM_sys_cdo)
485      # Example of what we want to catch : only filenames in those command lines
486      # IGCM_sys_ncrcat -O -v ${list_var_final_ncrcat} ${OUT_SE[*]} ${RESULT_SE}
487      # IGCM_sys_ncrcat --hst -v ${liste_coord}${var} ${file1} ${liste_file_tmp[*]} ${file_out}
488      # IGCM_sys_ncrcat -p ${dir} ${liste_file_tmp} --output ${output}
489      # IGCM_sys_ncrcat -x -v ${list_var} -p ${dir} ${liste_file_tmp} --output ${output}
490      instrumentation=true
[941]491      keepGoing=true
492      prefix=.
[926]493      i=0
494      while ( ${keepGoing} ) ; do
[941]495        # the last one is not interesting
496        if [ ${i} -eq ${#arguments[*]}-1 ] ; then
497          keepGoing=false
498        # look after "-p" option. Path prefix is the following arguments
499        elif [ ${arguments[${i}]} = "-p" ] ; then
[926]500          ((i = i + 1))
[941]501          prefix=${arguments[${i}]}
502          ((i = i + 1))
503        elif [ ${i} -eq ${#arguments[*]}-1 ] ; then
[926]504          keepGoing=false
[941]505        # looking for files
506        elif [ -f ${prefix}/${arguments[${i}]} ] ; then
507          fileList="${fileList} ${prefix}/${arguments[${i}]}"
508          ((i = i + 1))
509        # other options are not interesting
[926]510        else
511          ((i = i + 1))
512        fi
513      done
[941]514
[926]515      # i value is at least 1
516      # just need one file to get the directory
[941]517      source=$( echo ${fileList} | gawk '{print $1}' )
[926]518      # Nothing but the latest
519      dest=${arguments[${#arguments[*]}-1]}
520      # Size of file whose name are stored in a list
521      entitySize=$( IGCM_debug_sizeOfTabContent fileList ${dest} )
522      ;;
[913]523    esac
524
525    # Print information related to instrumentation
[915]526    ( ${instrumentation} ) && IGCM_debug_PrintInfosActions ${command} ${entitySize} ${startTime_ms} ${endTime_ms} ${dest} ${source}
[913]527
528    # Only cosmetics : stack file
[1115]529    if [ X${ActivateStackFilling} = Xtrue ] ; then
530      decal=0
531      while [ ${decal} -lt ${IGCM_debug_LenStack} ]; do
532        printf ' ' >> ${StackFileLocation}/${StackFileName}
533        (( decal = decal + 1 ))
534      done
535    fi
[2]536
[855]537    if ( ${ExitFlag} ) ; then
538      # Inform the stack file
[1115]539      if [ X${ActivateStackFilling} = Xtrue ] ; then
540        echo '!!! ExitFlag has been activated !!!' >> ${StackFileLocation}/${StackFileName}
541      fi
[874]542
[1216]543      # Unplugged message 4900 handling for now. To ease downstream treatment.
[1244]544      if [ X${ActivateBigBro} = Xtrue ] ; then
545        if [ X${TaskType} = Xcomputing ]; then
546          # RabbitMQ message code "COMPUTING JOBs COMMAND FAILURE"
547          code=1900
548        elif [ X${TaskType} = Xpost-processing ]; then
549          # RabbitMQ message code "POST-PROCESSING JOBs COMMAND FAILURE"
550          code=2900
551        elif [ X${TaskType} = Xchecking ]; then
552          # RabbitMQ message code "POST-PROCESSING FROM CHECKER JOBs COMMAND FAILURE"
553          code=3900
554        fi
555        # RabbitMQ message body
556        Body=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"msgUID\":\"$(uuidgen)\",\"command\":\"${command}\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" )
557
558        # Fill the rabbitMQ queue
559        IGCM_debug_sendAMQP
560      fi
[855]561    else
562      # Inform the stack file
[1115]563      if [ X${ActivateStackFilling} = Xtrue ] ; then
564        echo "< ${IGCM_debug_LenStack} : ${@}" >> ${StackFileLocation}/${StackFileName}
565      fi
[855]566    fi
567
[913]568    # Reset array if necessary
[544]569    if [ ${IGCM_debug_LenStack} = 0 ]; then
570      #echo
571      #IGCM_debug_Print 3 "Clean stack array"
572      #echo
573      unset IGCM_debug_Stack
574      unset IGCM_debug_StackArgs
[913]575      unset IGCM_debug_StackTiming
[544]576      IGCM_debug_Stack[0]=${NULL_STR}
577      IGCM_debug_StackArgs[0]=${NULL_STR}
[913]578      IGCM_debug_StackTiming[0]=${NULL_STR}
[2]579    fi
[544]580  fi
[869]581  #IGCM_debug_CallStack
[2]582}
583
584#D-#==================================================================
[1189]585#D-function IGCM_debug_BigBro_Initialize
[855]586#D-* Purpose: switch rabbitMQ on
587#D-
[1189]588function IGCM_debug_BigBro_Initialize {
589  IGCM_debug_PushStack "IGCM_debug_BigBro_Initialize"
[855]590
[1244]591  typeset postProcessingIDLength postProcessingName postProcessingDate postProcessingDimn postProcessingComp postProcessingFile
[1229]592
[1051]593# Message type standard fields:
594# https://github.com/Prodiguer/prodiguer-docs/wiki/MQ-Standard-Message-Fields
595
596# Message type dictionnary and custom fields:
597# https://github.com/Prodiguer/prodiguer-docs/wiki/Monitoring-Message-Dictionary
598
[868]599  if [ X${BigBrother} = Xtrue ] ; then
[1051]600    # create a unique ID for this specific job
601    jobuid=$(uuidgen)
[913]602
[1244]603    # get the assigned id by the scheduler for that job
604    IGCM_sys_getJobSchedulerID jobSchedulerID
605
[1189]606    if [ X${TaskType} = Xcomputing ]; then
607      if ( ${FirstInitialize} ) ; then
608        # RabbitMQ message code "BEGIN A SIMULATION"
609        code=0000
610        # create and persist a unique id for this simulation
611        simuid=$(uuidgen)
612        IGCM_card_WriteOption ${SUBMIT_DIR}/run.card Configuration simuid ${simuid}
613        # Standard fields for the first message
[1244]614        genericSimulationID=$( echo "\"msgApplication\":\"monitoring\",\"msgProducer\":\"libigcm\",\"msgProducerVersion\":\"${libIGCMVersion}\",\"activity\":\"IPSL\",\"name\":\"${config_UserChoices_JobName}\",\"experiment\":\"${config_UserChoices_ExperimentName}\",\"space\":\"${config_UserChoices_SpaceName}\",\"model\":\"${config_UserChoices_TagName}\",\"startDate\":\"${config_UserChoices_DateBegin}\",\"endDate\":\"${config_UserChoices_DateEnd}\",\"login\":\"${LOGIN}\",\"centre\":\"${CENTER}\",\"machine\":\"${MASTER}\",\"simuid\":\"${simuid}\",\"jobuid\":\"${jobuid}\"" )
[1189]615        # RabbitMQ message body with specific fields associated message codes treated here
[1277]616        Body=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"accountingProject\":\"${PROJECT}\",\"jobWarningDelay\":\"${jobWarningDelay}\",\"jobSchedulerID\":\"${jobSchedulerID}\",\"jobSubmissionPath\":\"${SUBMIT_DIR}\",\"msgUID\":\"$(uuidgen)\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" )
[1189]617        # Fill the rabbitMQ queue (the config.card in use will be sent)
618        IGCM_debug_sendAMQP activate
619      else
620        # RabbitMQ message code "A NEW COMPUTING JOB IS RUNNING PART OF A SIMULATION"
621        code=1000
622        # retrieve this simulation's unique id
623        IGCM_card_DefineVariableFromOption ${SUBMIT_DIR}/run.card Configuration simuid
624        simuid=${run_Configuration_simuid}
625        # Using standard fields for message others than the first one. Still subject to change
[1244]626        genericSimulationID=$( echo "\"msgApplication\":\"monitoring\",\"msgProducer\":\"libigcm\",\"msgProducerVersion\":\"${libIGCMVersion}\",\"simuid\":\"${simuid}\",\"jobuid\":\"${jobuid}\"" )
[1189]627        # RabbitMQ message body with specific fields associated message codes treated here
[1244]628        Body=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"accountingProject\":\"${PROJECT}\",\"jobWarningDelay\":\"${jobWarningDelay}\",\"jobSchedulerID\":\"${jobSchedulerID}\",\"jobSubmissionPath\":\"${SUBMIT_DIR}\",\"msgUID\":\"$(uuidgen)\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" )
[1189]629        # Fill the rabbitMQ queue
630        IGCM_debug_sendAMQP
631      fi
632
633      # NOT VERY NICE BUT ... IT WORKS
634      # Be sure that the genericSimulationID will be small from now on
635      # Using standard fields for messages others than the first one. Still subject to change
[1244]636      genericSimulationID=$( echo "\"msgApplication\":\"monitoring\",\"msgProducer\":\"libigcm\",\"msgProducerVersion\":\"${libIGCMVersion}\",\"simuid\":\"${simuid}\",\"jobuid\":\"${jobuid}\"" )
[1189]637
638    elif [ X${TaskType} = Xpost-processing ]; then
639      # RabbitMQ message code "A NEW POST-PROCESSING JOB IS RUNNING PART OF A SIMULATION"
640      code=2000
[1076]641      # retrieve this simulation's unique id
[1051]642      IGCM_card_DefineVariableFromOption ${SUBMIT_DIR}/run.card Configuration simuid
[1086]643      simuid=${run_Configuration_simuid}
[1076]644      # Using standard fields for message others than the first one. Still subject to change
[1244]645      genericSimulationID=$( echo "\"msgApplication\":\"monitoring\",\"msgProducer\":\"libigcm\",\"msgProducerVersion\":\"${libIGCMVersion}\",\"simuid\":\"${simuid}\",\"jobuid\":\"${jobuid}\"" )
[1229]646     
647      # Specify the post-processing task we are dealing with
[1244]648      postProcessingIDLength=$( echo "${Script_Post_Output}" | tr -d -c "\." | wc -c )
[1229]649      postProcessingName=$( echo "${Script_Post_Output}" | gawk -F. '{print $1}' )
650      postProcessingDate=$( echo "${Script_Post_Output}" | gawk -F. '{print $2}' )
[1231]651      postProcessingDimn="null"
652      postProcessingComp="null"
653      postProcessingFile="null"
[1254]654      if [ ${postProcessingIDLength} -eq 2 ] ; then
[1231]655        postProcessingDimn=$( echo "${Script_Post_Output}" | gawk -F. '{print $3}' )
[1254]656      elif [ ${postProcessingIDLength} -eq 4 ] ; then
[1229]657        postProcessingComp=$( echo "${Script_Post_Output}" | gawk -F. '{print $4}' )
658        postProcessingFile=$( echo "${Script_Post_Output}" | gawk -F. '{print $5}' )
659      fi
660
[1087]661      # RabbitMQ message body with specific fields associated message codes treated here
[1244]662      Body=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"accountingProject\":\"${PROJECT}\",\"jobWarningDelay\":\"${jobWarningDelay}\",\"jobSchedulerID\":\"${jobSchedulerID}\",\"jobSubmissionPath\":\"${SUBMIT_DIR}\",\"msgUID\":\"$(uuidgen)\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\",\"postProcessingName\":\"${postProcessingName}\",\"postProcessingDate\":\"${postProcessingDate}\",\"postProcessingDimn\":\"${postProcessingDimn}\",\"postProcessingComp\":\"${postProcessingComp}\",\"postProcessingFile\":\"${postProcessingFile}\"}" )
[1087]663      # Fill the rabbitMQ queue
664      IGCM_debug_sendAMQP
[855]665    fi
[913]666    # Turn the flag on
[855]667    ActivateBigBro=true
668  fi
[1189]669  IGCM_debug_PopStack "IGCM_debug_BigBro_Initialize"
[855]670}
671
672#D-#==================================================================
[1189]673#D-function IGCM_debug_BigBro_Finalize
674#D-* Purpose: Finalize rabbitMQ messages exchanges
675#D-
676function IGCM_debug_BigBro_Finalize {
677  IGCM_debug_PushStack "IGCM_debug_BigBro_Finalize"
678
679  # Message type standard fields:
680  # https://github.com/Prodiguer/prodiguer-docs/wiki/MQ-Standard-Message-Fields
681
682  # Message type dictionnary and custom fields:
683  # https://github.com/Prodiguer/prodiguer-docs/wiki/Monitoring-Message-Dictionary
684
685  if ( $DEBUG_debug ) ; then
686    if [ X${ActivateBigBro} = Xtrue ] ; then
687      if [ X${TaskType} = Xcomputing ]; then
688        if ( ${simulationIsOver} ) ; then
689          # RabbitMQ message code "SIMULATION ENDS"
690          code=0100
691          FlushAMQP=true
[1207]692        elif ( ${ExitFlag} ) ; then
693          # RabbitMQ message code "EXIT THE JOBS BECAUSE ERROR(S) HAS BEEN TRIGGERED"
[1244]694          code=1999
[1207]695          FlushAMQP=true
[1189]696        else
697          # RabbitMQ message code "COMPUTING JOB ENDS"
698          code=1100
699        fi
700      elif [ X${TaskType} = Xpost-processing ]; then
[1207]701        if ( ${ExitFlag} ) ; then
702          # RabbitMQ message code "POST-PROCESSING JOB FAILS"
[1244]703          code=2999
[1207]704          FlushAMQP=true
[1244]705        else
[1207]706          # RabbitMQ message code "POST-PROCESSING JOB ENDS"
707          code=2100
708          FlushAMQP=true
709        fi
[1244]710      elif [ X${TaskType} = Xchecking ]; then
711        if ( ${ExitFlag} ) ; then
712          # RabbitMQ message code "POST-PROCESSING JOB FAILS"
713          code=3999
714          FlushAMQP=true
715        else
716          # RabbitMQ message code "POST-PROCESSING JOB ENDS"
717          code=3100
718          FlushAMQP=true
719        fi
[1189]720      fi
721      # RabbitMQ message body
722      Body=$( echo "{${genericSimulationID},\"msgCode\":\"${code}\",\"msgUID\":\"$(uuidgen)\",\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" )
723      # Fill the rabbitMQ queue
724      IGCM_debug_sendAMQP
725    fi
726  fi
727 
728  IGCM_debug_PopStack "IGCM_debug_BigBro_Finalize"
729}
730
731#D-#==================================================================
[2]732#D-function IGCM_debug_Exit
733#D-* Purpose: Print Call Stack and set ExitFlag to true
734#D-
735function IGCM_debug_Exit {
[544]736  IGCM_debug_PushStack "IGCM_debug_Exit"
737  echo "IGCM_debug_Exit : " "${@}"
[913]738  echo
[894]739  echo "!!!!!!!!!!!!!!!!!!!!!!!!!!"
[913]740  echo "!!   ERROR TRIGGERED    !!"
741  echo "!!   EXIT FLAG SET      !!"
742  echo "!------------------------!"
743  echo
[894]744  IGCM_debug_CallStack
[544]745  ExitFlag=true
746  IGCM_debug_PopStack "IGCM_debug_Exit"
[2]747}
748
749#D-#==================================================
750#D-function IGCM_debug_Verif_Exit
751#D-* Purpose: exit with number 1 if ExitFlag is true
752#D-
753function IGCM_debug_Verif_Exit {
[544]754  if ( ${ExitFlag} ) ; then
[1206]755    echo "IGCM_debug_Verif_Exit : Something wrong happened previously."
[1207]756    echo "IGCM_debug_Verif_Exit : ERROR and EXIT keyword will help find out where."
[1206]757    # Only computing TaskType stops the job for now.
758    if [ X${TaskType} = Xcomputing ] ; then
[775]759      IGCM_card_WriteOption ${SUBMIT_DIR}/run.card Configuration PeriodState "Fatal"
760      echo "                        EXIT THE JOB."
761      echo
[869]762      IGCM_debug_CallStack
[874]763
[1206]764      # Mail notification
765      IGCM_sys_SendMail
[913]766
[1207]767      # Inform the rabbitMQ queue
768      IGCM_debug_BigBro_Finalize
769
[544]770      # And Good Bye
771      date
772      exit 1
[1206]773
774    elif [ X${TaskType} = Xpost-processing ] ; then
775      # If SpaceName is PROD then we stop when post_processing failed
776      if [ X${config_UserChoices_SpaceName} = XPROD ] ; then
[1220]777        echo "                        EXIT THE POST-PROCESSING JOB."
[1206]778        echo
[1207]779        IGCM_debug_CallStack
780
[1220]781        # Notify the computing job that something wrong happened.
[1280]782        IGCM_card_WriteOption ${SUBMIT_DIR}/run.card Configuration PeriodState "Fatal ${Script_Post_Output}"
[1220]783
[1206]784        # Mail notification?
785        #IGCM_sys_SendMailPost
[1207]786
787        # Inform the rabbitMQ queue
788        IGCM_debug_BigBro_Finalize
789
[1206]790        # And Good Bye
791        date
792        exit 1
793      else
[1207]794        echo "In config.card the variable SpaceName is not in PROD"
[1206]795        echo "              SO WE DO NOT EXIT THE JOB."
796        echo
797        date
798      fi
799    elif [ X${TaskType} = Xchecking ] ; then
[1207]800      echo "Nothing will happen for now"
[2]801    fi
[544]802  fi
[2]803}
804
805#D-#==================================================================
806#D-function IGCM_debug_Print
807#D-* Purpose: Print arguments according to a level of verbosity.
808#D-
809function IGCM_debug_Print
810{
[544]811  typeset level=$1
812  shift
813
814  if [ X"${1}" = X"-e" ]; then
815    typeset cmd_echo="echo -e"
[2]816    shift
[544]817  else
818    typeset cmd_echo="echo"
819  fi
[2]820
[544]821  if [ ${level} -le ${Verbosity} ] ; then
822    typeset i
823    case "${level}" in
824    1) for i in "$@" ; do
[734]825      ${cmd_echo} $(date +"%Y-%m-%d %T") "--Debug1-->" ${i}
[913]826      done ;;
[544]827    2) for i in "$@" ; do
[734]828      ${cmd_echo} $(date +"%Y-%m-%d %T") "--------Debug2-->" ${i}
[913]829      done ;;
[544]830    3) for i in "$@" ; do
[734]831      ${cmd_echo} $(date +"%Y-%m-%d %T") "--------------Debug3-->" ${i}
[913]832      done ;;
[544]833    esac
834  fi
[2]835}
836
837#D-#==================================================================
838#D-function IGCM_debug_PrintVariables
839#D-* Purpose: Print arguments when match a pattern
840#D-           according to a level of verbosity.
841function IGCM_debug_PrintVariables
842{
[544]843  typeset level=$1
844  shift
[2]845
[830]846  list=$( set | grep ^$1 | sed -e "s/'//g" )
[54]847
[544]848  if [ "X${list}" != X ]  ; then
849    IGCM_debug_Print ${level} ${list}
850  fi
[2]851}
852
853#D-#==================================================================
[914]854#D-function IGCM_debug_PrintInfosActions
[913]855#D-* Purpose: Print information related to instrumentation
856function IGCM_debug_PrintInfosActions
857{
858  typeset actionType=$1
859  typeset entitySize=$2
860  typeset start_ms=$3
861  typeset end_ms=$4
862
863  typeset dest=$5
864  typeset source=$6
865
866  typeset diff_ms entitySizeKo entitySizeMo flux_Ko_ms flux_Ko_s flux_Mo_s
[1090]867  typeset dirFrom dirTo
[913]868
869  diff_ms=$(( $end_ms - $start_ms ))
870  # echo "diff_ms=$diff_ms"
871
872  entitySizeKo=$( echo ${entitySize} | gawk -F"|" '{print $1}' )
873  # echo "entitySizeKo=$entitySizeKo"
874  entitySizeMo=$( echo ${entitySize} | gawk -F"|" '{print $2}' )
875
876  # flux en Ko / ms
877  flux_Ko_ms=$( echo "scale=6;${entitySizeKo}/${diff_ms}" | bc )
878  # echo "flux_Ko_ms=$flux_Ko_ms"
879
880  # flux en Ko / s
881  flux_Ko_s=$(( $flux_Ko_ms * 1000 ))
882  # echo "flux_Ko_s=$flux_Ko_s"
883
884  # flux en Mo / s
885  flux_Mo_s=$( echo "scale=6;${flux_Ko_s}/1024" | bc )
886  # echo "flux_Mo_s=$flux_Mo_s"
887
888  if [ -d $dest ] ; then
[1090]889    dirTo=$( readlink -f ${dest} )
[913]890  else
[1090]891    dirTo=$( readlink -f $( dirname ${dest} ) )
[913]892  fi
893
894  if [ -d $source ] ; then
[1090]895    dirFrom=$( readlink -f ${source} )
[913]896  else
[1090]897    dirFrom=$( readlink -f $( dirname ${source} ) )
[913]898  fi
899
[1094]900  instrumentationContent=$( echo "\"actionName\":\"${actionType}\",\"size_Mo\":\"${entitySizeMo}\",\"duration_ms\":\"${diff_ms}\",\"throughput_Mo_s\":\"${flux_Mo_s}\",\"dirFrom\":\"${dirFrom}\",\"dirTo\":\"${dirTo}\"" )
[1050]901
[1115]902  if [ X${ActivateStackFilling} = Xtrue ] ; then
903    echo "{${instrumentationContent}}" >> ${StackFileLocation}/${StackFileName}
904  fi
[1050]905
906  # Inform the rabbitMQ queue
907  if [ X${ActivateBigBro} = Xtrue ] ; then
908    # RabbitMQ message body
[1065]909    Body=$( echo "{${genericSimulationID},\"msgCode\":\"7000\",\"msgUID\":\"$(uuidgen)\",${instrumentationContent},\"msgTimestamp\":\"$( date +"%Y-%m-%dT%H:%M:%S.%N%z" )\"}" )
[1050]910    # Fill the rabbitMQ queue
911    IGCM_debug_sendAMQP
912  fi
[913]913}
914
915#D-#==================================================================
[2]916#D-function IGCM_debug_Check
917#D- * Purpose: Check the present file by comparison with a reference file
918function IGCM_debug_Check
919{
[544]920  #---------------------
921  if [ ! -n "${libIGCM}" ] ; then
922    echo "Check libIGCM_debug ..........................................[ FAILED ]"
923    echo "--Error--> libIGCM variable is not defined"
924    exit 2
925  fi
[2]926
[544]927  #---------------------
928  if [ ! -n "${Verbosity}" ] ; then
929    echo "Check libIGCM_debug ..........................................[ FAILED ]"
930    echo "--Error--> Verbosity variable is not defined"
931    exit 3
932  fi
[2]933
[544]934  #---------------------
[1118]935  # Need to remove timestamps here
936  diff ${libIGCM}/libIGCM_debug/IGCM_debug_Test.ref <(${libIGCM}/libIGCM_debug/IGCM_debug_Test.ksh | sed -e "s:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]\:[0-9][0-9]\:[0-9][0-9] ::g") > /dev/null 2>&1
[1106]937  status=$?
[2]938
[1118]939  if [ ${status} -eq 0 ] ; then
[544]940    echo "Check libIGCM_debug ..............................................[ OK ]"
941  else
942    echo "Check libIGCM_debug ..........................................[ FAILED ]"
943    echo "--Error--> Execution of ${libIGCM}/libIGCM_debug/IGCM_debug_Test.ksh"
944    echo "           has produced the file IGCM_debug_Test.ref.failed"
945    echo "           Please analyse differences with the reference file by typing:"
946    echo "           diff IGCM_debug_Test.ref.failed ${libIGCM}/libIGCM_debug/IGCM_debug_Test.ref"
947    echo "           Report errors to the author: Patrick.Brockmann@cea.fr"
[1118]948    diff ${libIGCM}/libIGCM_debug/IGCM_debug_Test.ref <(${libIGCM}/libIGCM_debug/IGCM_debug_Test.ksh | sed -e "s:[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]\:[0-9][0-9]\:[0-9][0-9] ::g")
[544]949    exit 4
950  fi
951  #---------------------
[2]952}
Note: See TracBrowser for help on using the repository browser.