#!/bin/bash
# fs_time

VERSION='$Id: fs_time,v 1.0 2024/03/08 15:12:00 kueglerd Exp $'
outfile=""
key="@#@FSTIME "
cmd=()
verbose=0

if [[ -z "$FSTIME_LOAD" ]]
then
  # Turn on by default
  export FSTIME_LOAD=1
fi

function usage()
{
  cat << EOF

fs_time [options] command args
 options:
  -o outputfile : save resource info into outputfile
  -k key
  -l : report on load averages as from uptime
EOF
}

function help()
{
  cat << EOF
This is a frontend for the unix /usr/bin/time program to keep track of
resources used by a process. The basic usage is like that of time, ie,

fs_time [options] command args

It creates a formatted output that allows for easy processing. See below.

If the env variable FSTIME_LOAD is set to 1 or not set at all, then
uptime is run before and after each process to give the load on the
system (see below for output)

Default fs_time Output (see also the manual page for /usr/bin/time):
1. Key (default is @#@FSTIME)
2. Time stamp at the onset of execution
3. Command name
4. N Number of arguments passed to command
5. e Elapsed real time in seconds . This is the total
      amount of time from when the command started to when it ended regardless
      of how much of the CPU it got.
6. S Total number of CPU-seconds that the process spent in kernel mode.
7. U Total number of CPU-seconds that the process spent in user mode.
8. P Percentage of the CPU that this job got, computed as (U+S)/e.
9. M Maximum resident set size of the process during its lifetime, in Kbytes.
10. F Number  of major page faults that occurred while the process was running.
      These are faults where the page has to be read in from disk.
11. R Number of minor, or recoverable, page faults.  These are
   faults for pages that are not valid but which have not yet been
   claimed by other virtual pages.  Thus the data in the page is
   still valid but the system tables must be updated.
12.  W Number of times the process was swapped out of main memory.
13. c Number of times the process was context-switched involuntarily
    (because the time slice expired).
14. w Number of waits: times that the program was context-switched voluntarily,
    for instance while  waiting  for an I/O operation to complete.
15. I Number of file system inputs by the process.
16. O Number of file system outputs by the process.
17. L L1 L5 L15 : load averages at 1, 5, and 15 min (with setenv FSTIME_LOAD 1)

Example:

fs_time -o resource.dat mri_convert orig.mgz myfile.mgz
mri_convert orig.mgz myfile.mgz
reading from orig.mgz...
TR=2730.00, TE=3.44, TI=1000.00, flip angle=7.00
i_ras = (-1, 0, 0)
j_ras = (2.38419e-07, 0, -1)
k_ras = (-1.93715e-07, 1, 0)
writing to myfile.mgz...
@#@FSTIME 2016:01:21:18:27:08 mri_convert N 2 e 2.20 S 0.05 U 1.64 P 77% M 23628 F 0 R 5504 W 0 c 7 w 3 I 0 O 20408

The above command runs the mri_convert command with two arguments and
produces the information about resources. It also creates a file
resource.dat with the resource information. In this case, the above is
interpreted as:

@#@FSTIME  : key for easy searching/grepping
mri_convert : command that was run
2016:01:21:18:27:08 : time stamp at the onset of execution year:month:day:hour:min:sec
N 2 : mri_convert was run with 2 arguments
e 2.20 : total elapsed time in seconds from start to end
S 0.05 : seconds spent in system mode
U 1.64 : seconds specnt in user mode
P 77%  : percent of cpu that process used (S+U)/e
M 23628 : maximum memory size in Kbytes
F 0 : no major page faults
R 5504 : number of minor page faults
W 0 : process was not swapped out of memory
c 7 : seven involuntary context-switches
w 3 : three voluntary context-switches
I 0 : zero file system inputs by the process.
O 20408 : Number of file system outputs by the process.

If the env variable FSTIME_LOAD is set to 1, the output looks something like

@#@FSLOADPOST 2016:01:23:15:11 mri_convert N 2 0.00 0.03 0.06

The 3 numbers are the system load averages for the past 1, 5, and 15
minutes as given by uptime.
EOF
}

function   arg1err()
{
  # param 1 : flag
  echo "ERROR: flag $1 requires one argument"
  exit 1
}

inputargs=("$@")
any_help=$(echo "$@" | grep -e -help)
if [[ -n "$any_help" ]]
then
  usage
  help
  exit 0
fi
any_version=$(echo "$@" | grep -e -version)
if [[ -n "$any_version" ]]
then
  echo "$VERSION"
  exit 0
fi

# sourcing FreeSurfer should not be needed at this point,
# source $FREESURFER_HOME/sources.sh

cmdline=("$@")
while [[ $# != 0 ]]
do

  flag=$1
  shift

  case $flag in
    -o)
      if [[ "$#" -lt 1 ]] ; then arg1err "$flag"; fi
      outfile=$1
      shift
      ;;
    -k)
      if [[ "$#" -lt 1 ]] ; then arg1err "$flag" ; fi
      key=$1
      shift
      ;;
    -l|-load)
      export FSTIME_LOAD=1
      ;;
    -no-load)
      export FSTIME_LOAD=0
      ;;
    -debug)
      verbose=1
      ;;
    *)
      # must be at the start of the command to run
      # put item back into the list
      cmd=("$flag" "$@")
      break
      ;;
  esac

done

if [[ "$verbose" == 1 ]]
then
  echo "Parameters to fs_time:"
  if [[ -n "$outfile" ]] ; then echo "-o $outfile" ; fi
  if [[ "$key" != "@#@FSTIME " ]] ; then echo "-k $key" ; fi
  echo "FSTIME_LOAD=$FSTIME_LOAD"
  echo "command:"
  echo "${cmd[*]}"
  echo ""
fi

# CHECK PARAMS
if [[ "${#cmd[@]}" == 0 ]]
then
  usage
  echo "ERROR: no command passed to execute"
  exit 1
fi

if [[ ! -e /usr/bin/time ]]
then
  echo "ERROR: cannot find /usr/bin/time"
  exit 1
fi

command="${cmd[0]}"
npyargs=0
# remove python from command
if [[ "$command" =~ python(3(.[0-9]+)?)?$ ]]
then
  npyargs=1
  for c in "${cmd[@]:1}" ; do
    if [[ ! "$c" =~ ^- ]] ; then command="$c" ; break ; fi
    npyargs=$((npyargs + 1))
  done
fi
# remove $FASTSURFER_HOME path from command
command_short="${command:0:${#FASTSURFER_HOME}}"
if [[ -n "$FASTSURFER_HOME" ]] && [[ "$command_short" == "$FASTSURFER_HOME" ]]
then
  command="${command:${#command_short}}"
  while [[ "${command:0:1}" == "/" ]] ; do command="${command:1}" ; done
fi

nargs=$((${#cmd[@]} - 1 - npyargs))

function make_string ()
{
  # param 1 : key
  # param 2 : command
  # param 3 : num args
  dt=$(date '+%Y:%m:%d:%H:%M:%S')
  uptime_data_array=($(uptime | sed 's/,/ /g'))
  echo "$1 $dt $2 N $3 ${uptime_data_array[-3]} ${uptime_data_array[-2]} ${uptime_data_array[-1]}"
  export upt="L ${uptime_data_array[-3]} ${uptime_data_array[-2]} ${uptime_data_array[-1]}"
}

if [[ "$FSTIME_LOAD" == 1 ]]
then
  make_string "@#@FSLOADPRE" "$command" "$nargs"
else
  upt=""
fi

dt=$(date '+%Y:%m:%d:%H:%M:%S')
fmt="$key $dt $command N $nargs e %e S %S U %U P %P M %M F %F R %R W %W c %c w %w I %I O %O $upt"

timecmd=("/usr/bin/time")
if [[ -n "$outfile" ]] ; then timecmd=("${timecmd[@]}" -o "$outfile"); fi
"${timecmd[@]}" -f "$fmt" "${cmd[@]}"
st=$?
if [[ -n "$outfile" ]] ; then cat $outfile; fi

if [[ "$FSTIME_LOAD" == 1 ]]
then
  make_string "@#@FSLOADPOST" "$command" "$nargs"
fi

exit $st
