#!/bin/bash
#
# Initiate a JVM dump
#
# Description: Issue the JVMDumper command to dump the specified jvm
#
# Change Activity:
#   01/15/2003 M. Uglow - initial version
#   01/21/2003 M. Uglow - added description argument
#   01/31/2003 M. Uglow - added check for number of dump files.  if there are more than 3
#                         after the dump completes, the oldest will be removed
#   07/01/2003 M. Uglow - fixed problem with filename generation
#   11/25/2003 J. Hennessy - Changed date command to prevent misinterpretation
#                            by CMVC.  Quoted some vars.  Changed handling of
#                            output dir so it doesn't have to end with a path
#                            separator.  Switched order of description and
#                            directory so description could be omitted or made
#                            up of many arguments.  Pass -properties and -gui
#                            arguments.  Minimized dumper's classpath.  Changed
#                            upper limit on dump files to 6.
#   12/08/2003 J. Hennessy - Changed syntax to be options instead of positional
#                            parameters.  Changed classpath generation to find
#                            ravelDump.jar in the correct location for official
#                            builds.  Changed name and location of temporary
#                            file created to hold stdout/stderr of dumper.
#                            Added option to compress dump file.  Made dump
#                            file limit checking optional.
#
#STARTUSAGE
#
# Usage:
#   jvmdump [-h hostname] -p port [-o output] [-d description] [-c] [-l limit]
#           [-t]
#
# Where:
#   -h   The host name or IP address of the machine running the JVM to dump.
#        If omitted, it defaults to "localhost".
#
#   -p   The TCP port on which the JVM to be dumped is listening for debugger
#        connections.  This option is required.
#
#   -o   The output directory or filename.  If the argument refers to an
#        existing directory, or ends with a trailing "/", the name of the dump
#        file will be set to "HMCPportDyyyymmddThhmmss.rdm", where "port" is
#        the port that is used to attach to the JVM, and "yyyymmdd" is the
#        year/month/day, and "hhmmss" is the hours/minutes/seconds, in 24-hour
#        format.  If that argument does not refer to a directory, it is taken
#        as the complete output file name, in which case, if it does not end in
#        ".rdm", the ".rdm" suffix will be appended before using it.  If
#        omitted, this option defaults to the current working directory, which
#        means the filename will be automatically generated as described.
#
#   -d   A description to be saved in the dump.  The description is available
#        to anybody viewing the dump.  There is no limit on the size of the
#        description, and it can contain embedded newline characters to denote
#        line boundaries, but generally a short string is specified.  Note that
#        this is a single argument, so the description should be properly
#        quoted.
#
#   -c   Compress the dump file after creating it.  When this option is
#        specified, the bzip2 utility is used to compress the file.  Since
#        dump files are quite compressible, significant disk space or network
#        bandwidth can be saved by specifying this option.  It can, however,
#        take a fair amount of time to compress a dump file, so if it's
#        important to resume the target virtual machine as soon as possible,
#        then it's better not to specify this option, and manually to compress
#        the dump file asynchronously.  Note that if the -o option is used to
#        name an output file, then that file name is the one used for the dump
#        file, but after running the bzip2 utility, the final result file will
#        have ".bz2" appended to the end.
#
#   -l   Limit the number of dump files in the specified output directory to
#        the number specified as an argument.  Specify the number of dump
#        files to be retained.  This option is ignored is -o is specified as
#        a file name instead of a directory.
#
#   -t   Trace the errors to the HMC internal trace buffer.  This option is
#        useful when the script is being run by the HMC instead of a person.
#
# This script will initiate a Java Virtual Machine dump, meaning that the
# target JVM is suspended, then information is captured on every thread that is
# running and the call stack of each thread, every class that is loaded, and
# every object that is referenced directly or indirectly by any running thread
# or any static variable of a loaded class.  When all information is captured,
# the target JVM is resumed.  Since the target JVM is suspended while the dump
# is being captured, and the dumping process may take several minutes, the
# target JVM will appear "hung" during this interval.
#
# A graphical progress indication will be displayed on the screen while the
# dump is being captured.  Options specifying non-default dumping behavior are
# read from data/RavelDump.properties.  The script will not return until the
# dump has been captured and the dump file has been written to disk.
#
#ENDUSAGE
#****************************************************************************

function showOutput() {
   echo "$1"
   if [[ $trace == 1 ]]; then
      actzTrace "XJDMPSSF: $1"
   fi
}

function showError() {
   echo "$1" >&2
   if [[ $trace == 1 ]]; then
      actzTrace "XJDMPSSF: $1"
   fi
}

hostname="localhost"
port=""
outputdir="."
description=""
compress=0
let dumpsLimit=0
giveUsage=0
trace=0

# Parse the options
while getopts 'h:p:o:d:cl:t?' optname; do
   case "$optname" in
      h) hostname="$OPTARG";;
      p) port="$OPTARG";;
      o) outputdir="$OPTARG";;
      d) description="$OPTARG";;
      c) compress=1;;
      l) dumpsLimit=$OPTARG;;
      t) trace=1;;
      \?) giveUsage=1; break;;
   esac
done

if [ "$giveUsage" -eq 1 ]; then
   # Print out the prologue comments as usage info
   sed -e '/STARTUSAGE/,/ENDUSAGE/ s/^#//' -e '1,/STARTUSAGE/ d' -e '/ENDUSAGE/,$ d' "$0"
   exit 0
fi

# First positional parameter is at index OPTIND.  Shift so that these parms
# are easily accessible to following code.
if [ $OPTIND -ne 1 ]; then
   shift $(($OPTIND-1))
fi

if [[ $trace == 1 ]]; then
   actzTrace "XJDMPSSF: -> jvmdump"
fi

if [[ -z "$port" ]]; then
   showError "The -p option must be specified to indicate the TCP port to attach to."
   echo "Issue jvmdump -? to see usage information.">&2
   exit 1
fi

if [ -z "$JDKHOME" ]; then
   JDKHOME=$(/usr/bin/which java 2>/dev/null)
   if [ -z "$JDKHOME" ]; then
      showError "Unable to determine location of the JDK."
      exit 1
   fi

   JDKHOME=$(dirname $(dirname $JDKHOME))
fi


if [ -n "$CONSOLE_PATH" ]; then
   topdir="$CONSOLE_PATH"
else
   if [ -n "$HWMCAHOME" ]; then
      topdir="$HWMCAHOME"
   else
      showError "Neither environment variable CONSOLE_PATH or HWMCAHOME is set."
      exit 1
   fi
fi

if [[ -r "$topdir/jars/framework/ravelDump.jar" ]]; then
   ravelDumpJar="$topdir/jars/framework/ravelDump.jar"
else
   ravelDumpJar="$topdir/com/ibm/ravel/dump/ravelDump.jar"
fi

clspath="$ravelDumpJar:$JDKHOME/lib/tools.jar"

# determine if a description is to be placed in the dump
descriptKey=""
if [ -n "$description" ]; then
   descriptKey="-description"
fi

if [[ -d "$outputdir" || "${outputdir%/}" != "$outputdir" ]]; then
   mkdir -p "$outputdir"
   if (( $? != 0 )); then
      showError "Error creating directory $outputdir"
      exit 2
   fi

   # The strange formulation of the hours/minutes/second date command below is to
   # prevent misinterpretation by CMVC of the characters as SCCS keywords that it
   # should substitute when the file is extracted.  This is needed since '%'
   # followed by 'H' followed by '%' and '%' followed by 'M' followed by '%'
   # are SCCS keywords supported by CMVC.
   basefile="HMCP$port"D$(date +'%Y%m%d')"T"$(date +'%H''%M''%S')
   dumpfile="$outputdir/$basefile.rdm"
else
   dumpfile="$outputdir"
   if [[ "${dumpfile%\.rdm}" == "$dumpfile" ]]; then
      dumpfile="$dumpfile.rdm"
   fi

   if (( $dumpsLimit > 0 )); then
      showError "Ignoring -l option, since -o specified a file, not a directory."
      dumpsLimit=0
   fi
fi

if [[ -z "$TMPDIR" ]]; then
   TMPDIR='/tmp'
fi

# Temporary file to capture stdout and stderr from JVMDumper command
tempoutfile="$TMPDIR/jvmdump.$$"

$JDKHOME/bin/java -Xmx256m -Xms64m -cp $clspath JVMDumper -host "$hostname" \
   -port "$port" -output "$dumpfile" -properties "$topdir/data/RavelDump.properties" \
   -gui "$descriptKey" "$description" > "$tempoutfile" 2>&1
rc=$?

# Write the output from JVMDumper to the trace file and to stdout
while read line; do
   showOutput "$line"
done < "$tempoutfile"

rm "$tempoutfile"

if [[ $rc != 0 ]]; then
   showError "JVMDumper failed, rc = $rc"
   rm "$dumpfile"
   exit $rc
fi

# Compress the dump file if requested
if [[ "$compress" == 1 ]]; then
   showOutput "Compressing dump file..."
   bzip2 -zvf "$dumpfile"
   let bziprc=$?
   if (( $bziprc != 0 )); then
      showError "bzip2 failed with rc==$bziprc"
      exit $bziprc
   fi
fi

# If the caller asked for the number of dump files in the output directory to
# be limited, erase any old ones here.
if (( $dumpsLimit > 0 )); then
   alldumpfiles=$(ls -t "$outputdir"/*.rdm "$outputdir"/*.rdm.bz2)
   if [ $(echo "$alldumpfiles" | wc -w) -gt $dumpsLimit ]; then
      echo "$alldumpfiles" | sed "1,${dumpsLimit}d" | while read line; do
         showOutput "Removing dump file $line"
         rm "$line"
      done
   fi
fi

if [[ $trace == 1 ]]; then
   actzTrace "XJDMPSSF: <- jvmdump"
fi
exit $rc
