#!/usr/local/bin/bash
#
# Get an itrace and save it into /ifs/itraces
#
# Can save and restore the itrace categories context, if a new set of itrace
# categories is specified.

usage()
{
    cat <<EOF
Usage: isi_get_itrace [options] [itrace_name]

  [itrace_name] specifies the prefix for the trace file name. If it is not
  specified, a default of "unnamed" will be used.

Options:
  -c category1,category2,...  Capture using these categories.
  -C category1,category2,...  Capture using /only/ these categories
  -p preset                   Capture using a preset set of categories
                              "-p normal" is implied if no preset is specified
                              and -C is not used.
  -r cmd                      Run trace while executing cmd
  -n num_entries              The maximum number of entries to collect
                              (def: $CLI_OPTIONS_NUM_ENTRIES)
  -l                          Low frequency event logging: disables
                              thread switch event logging.

    Presets:
      stats                   Network/performance/IB statistics
      normal                  Filesystem and thread trace, with some stats
      mutex                   Mutex contention trace
      network                 Verbose network tracing
      all                     Ridiculous level of tracing
EOF
}

error_check_cli_options()
{
    # This doesn't check for "-C threadswitch -l". The "-l" will superseed the
    # "-C threadswitch".

    if [[ -n $CLI_OPTIONS_NON_EXCLUSIVE_CAT ]] && [[ -n $CLI_OPTIONS_EXCLUSIVE_CAT ]] ; then
        echo "Error: -c and -C options are mutually exclusive."
        exit 1
    fi

    if [[ -n $CLI_OPTIONS_EXCLUSIVE_CAT ]] && [[ -n $CLI_OPTIONS_PRESET ]] ; then
        echo "Error: -p and -C options are mutually exclusive."
        exit 1
    fi
}

itrace_setup()
{
    SAVE_MASK=`sysctl -n debug.ktr.mask`
    SAVE_CPUMASK=`sysctl -n debug.ktr.cpumask`
    SAVE_AFFINITY_KTR=`sysctl -n debug.ktr.buffer_affinity_ktr`
    SAVE_AFFINITY_ITRACE=`sysctl -n debug.ktr.buffer_affinity_itrace`
    SAVE_AFFINITY_UKTR=`sysctl -n debug.ktr.buffer_affinity_uktr`
    SAVE_NUM_ENTRIES=`sysctl -n debug.ktr.entries`

    # stop any existing trace from running and clear the buffer.
    # Since there is some implicit behavior with "syscall kern.itrace.running",
    # we want to explicitely start everything in the off state so that we
    # get the same behavior every time. The implicit behavior in the sysctl is
    # to associate itrace with the buffer if it was not already associeated
    # with the currently selected behavior.
    # The previous running state will not be restored. The final state will be
    # that neither itrace nor the ktr buffer are running.
    sysctl kern.itrace.running=0 > /dev/null
    sysctl debug.ktr.running=0 > /dev/null
    sysctl debug.ktr.mask=0 > /dev/null
    sysctl debug.ktr.cpumask=-1 > /dev/null
    sysctl debug.ktr.clear=1 > /dev/null
    sysctl debug.ktr.stop_counter=0 > /dev/null
    sysctl kern.itrace.do_wrap=0 > /dev/null

    SELECTED_BUFFER=`sysctl -n debug.ktr.selected_buffer`
    # We need to enable ktr because some of the messages expected with
    # ITRACE_THREADSWITCH actually come from KTR_SCHED. The two sources
    # share a mask, so as long as both itrace and ktr are logging messages
    # to the buffer, we will see both sources of message.
    # This will have a similar effect for KTR_VOP/ITRACE_VOP and
    # KTR_SYSC/ITRACE_SYSCALL.
    let "NEW_AFFINITY_KTR=$SAVE_AFFINITY_KTR | (1 << $SELECTED_BUFFER)"
    sysctl debug.ktr.buffer_affinity_ktr=$NEW_AFFINITY_KTR > /dev/null
    let "NEW_AFFINITY_ITRACE=$SAVE_AFFINITY_ITRACE | (1 << $SELECTED_BUFFER)"
    sysctl debug.ktr.buffer_affinity_itrace=$NEW_AFFINITY_ITRACE > /dev/null
    let "NEW_AFFINITY_UKTR=$SAVE_AFFINITY_UKTR & ~(1 << $SELECTED_BUFFER)"
    sysctl debug.ktr.buffer_affinity_uktr=$NEW_AFFINITY_UKTR > /dev/null
}

act_on_cli_options()
{
    # This function does not handle CLI_OPTIONS_RUN_COMMAND or CLI_OPTIONS_TRACENAME

    if [[ -z $CLI_OPTIONS_EXCLUSIVE_CAT ]] && [[ -z $CLI_OPTIONS_PRESET ]] ; then
        CLI_OPTIONS_PRESET="normal"
    fi

    if [[ -n $CLI_OPTIONS_NON_EXCLUSIVE_CAT ]] ; then
        set_categories "$CLI_OPTIONS_NON_EXCLUSIVE_CAT"
    fi

    if [[ -n $CLI_OPTIONS_EXCLUSIVE_CAT ]] ; then
        set_categories "$CLI_OPTIONS_EXCLUSIVE_CAT"
    fi

    if [[ -n $CLI_OPTIONS_PRESET ]] ; then
        set_preset "$CLI_OPTIONS_PRESET"
    fi

    sysctl debug.ktr.entries=$CLI_OPTIONS_NUM_ENTRIES > /dev/null

    # Turn on the low frequency itrace by disabling threadswitch logging
    if [[ $CLI_OPTIONS_LOWFREQ -eq 1 ]] ; then
        sysctl kern.itrace.category.threadswitch=0 > /dev/null
    fi

    if [[ -z $CLI_OPTIONS_TRACENAME ]] ; then
        CLI_OPTIONS_TRACENAME="unnamed"
    fi
}

itrace_teardown()
{
    sysctl debug.ktr.mask=$SAVE_MASK > /dev/null
    sysctl debug.ktr.cpumask=$SAVE_CPUMASK > /dev/null
    sysctl debug.ktr.buffer_affinity_ktr=$SAVE_AFFINITY_KTR > /dev/null
    sysctl debug.ktr.buffer_affinity_itrace=$SAVE_AFFINITY_ITRACE > /dev/null
    sysctl debug.ktr.buffer_affinity_uktr=$SAVE_AFFINITY_UKTR > /dev/null
    sysctl debug.ktr.entries=$SAVE_NUM_ENTRIES > /dev/null
}

set_categories()
{
    for a in ${1//,/ }; do
      sysctl kern.itrace.category.$a=1 > /dev/null
    done
}

set_preset()
{
    CATS=""
    case "$1" in
        stats)
                CATS="perfstat,netstat,ibstat,threadswitch"
        ;;
        normal)
                CATS="mutex,syscall,vop,profile,doalert,netdevstat,efs,signal,interrupt,netstat,ibstat,perfstat,clockstack,threadswitch"
        ;;
        mutex)
                CATS="mutex,threadswitch"
        ;;
        network)
                CATS="netstat,sockstat,threadswitch"
        ;;
        all)
                CATS="mutex,syscall,vop,profile,doalert,sockstat,netdevstat,net,netverb,efs,interrupt,signal,nfs,disk,ib,func,perfstat,netstat,ibstat,rpc,clockstack,lock,lockverb,threadswitch"
        ;;
    esac

    set_categories "$CATS"
}

# Begin

CLI_OPTIONS_NON_EXCLUSIVE_CAT=
CLI_OPTIONS_EXCLUSIVE_CAT=
CLI_OPTIONS_PRESET=
CLI_OPTIONS_RUN_COMMAND="sleep 24"
let "CLI_OPTIONS_NUM_ENTRIES=2**20"
CLI_OPTIONS_LOWFREQ=

while getopts 'hc:C:p:r:n:l' OPTION
do
    case $OPTION in
        h)
            usage; exit 1
        ;;
        c)
            CLI_OPTIONS_NON_EXCLUSIVE_CAT="$OPTARG"
        ;;
        C)
            CLI_OPTIONS_EXCLUSIVE_CAT="$OPTARG"
        ;;
        p)
            CLI_OPTIONS_PRESET="$OPTARG"
        ;;
        r)
            CLI_OPTIONS_RUN_COMMAND="$OPTARG"
        ;;
        n)
            CLI_OPTIONS_NUM_ENTRIES="$OPTARG"
        ;;
        l)
            CLI_OPTIONS_LOWFREQ=1
        ;;
    esac
done
shift $((OPTIND - 1))

CLI_OPTIONS_TRACENAME=$1

error_check_cli_options
itrace_setup
act_on_cli_options

USING_CATS=`sysctl kern.itrace.category | grep -v ": 0"`
echo -n "Using categories: "
for a in ${USING_CATS//1/}; do
    a=${a//kern.itrace.category./}
    echo -n "${a//:/} "
done
echo

echo -n "Starting trace... "

sysctl kern.itrace.running=1 > /dev/null

echo -n "Executing: \`$CLI_OPTIONS_RUN_COMMAND\` "
$CLI_OPTIONS_RUN_COMMAND
sysctl kern.itrace.running=0 > /dev/null
sysctl debug.ktr.running=0 > /dev/null
/usr/bin/isi_save_itrace $CLI_OPTIONS_TRACENAME

itrace_teardown

exit
