Convert env vars to cmd line opts. Use parse_options lib.

This commit is contained in:
Daniel Nichter
2011-11-29 11:54:47 -07:00
parent b93b7de472
commit 937c16c172

View File

@@ -4,6 +4,173 @@
# See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal # See "COPYRIGHT, LICENSE, AND WARRANTY" at the end of this file for legal
# notices and disclaimers. # notices and disclaimers.
# ###########################################################################
# log_warn_die package
# This package is a copy without comments from the original. The original
# with comments and its test file can be found in the Bazaar repository at,
# lib/bash/log_warn_die.sh
# t/lib/bash/log_warn_die.sh
# See https://launchpad.net/percona-toolkit for more information.
# ###########################################################################
set -u
EXIT_STATUS=0
log() {
TS=$(date +%F-%T | tr :- _);
echo "$TS $1"
}
warn() {
log "$1" >&2
EXIT_STATUS=$((EXIT_STATUS | 1))
}
die() {
warn "$1"
exit 1
}
# ###########################################################################
# End log_warn_die package
# ###########################################################################
# ###########################################################################
# parse_options package
# This package is a copy without comments from the original. The original
# with comments and its test file can be found in the Bazaar repository at,
# lib/bash/parse_options.sh
# t/lib/bash/parse_options.sh
# See https://launchpad.net/percona-toolkit for more information.
# ###########################################################################
set -u
declare -a ARGV # non-option args (probably input files)
declare EXT_ARGV # everything after -- (args for an external command)
OPT_ERR=${OPT_ERR:""}
usage() {
local file=$1
local usage=$(grep '^Usage: ' $file)
local opts=$(grep -A 2 '^=item --' $file | sed -e 's/^=item //' -e 's/^\([A-Z]\)/ \1/' -e 's/^--$//' > $TMPDIR/help)
if [ "$OPT_ERR" ]; then
echo "Error: ${OPT_ERR}" >&2
fi
echo $usage >&2
echo >&2
echo "Options:" >&2
echo >&2
cat $TMPDIR/help >&2
echo >&2
echo "For more information, 'man $TOOL' or 'perldoc $file'." >&2
}
parse_options() {
local file=$1
shift
local opt=""
local val=""
local default=""
local version=""
local i=0
awk '
/^=head1 OPTIONS/ {
getline
while ($0 !~ /^=head1/) {
if ($0 ~ /^=item --.*/) {
long_opt=substr($2, 3, length($2) - 2)
short_opt=""
required_arg=""
if ($3) {
if ($3 ~ /-[a-z]/)
short_opt=substr($3, 3, length($3) - 3)
else
required_arg=$3
}
if ($4 ~ /[A-Z]/)
required_arg=$4
getline # blank line
getline # short description line
if ($0 ~ /default: /) {
i=index($0, "default: ")
default=substr($0, i + 9, length($0) - (i + 9))
}
else
default=""
print long_opt "," short_opt "," required_arg "," default
}
getline
}
exit
}' $file > $TMPDIR/options
while read spec; do
opt=$(echo $spec | cut -d',' -f1 | sed 's/-/_/g' | tr [:lower:] [:upper:])
default=$(echo $spec | cut -d',' -f4)
eval "OPT_${opt}"="$default"
done < <(cat $TMPDIR/options)
for opt; do
if [ $# -eq 0 ]; then
break
fi
opt=$1
if [ "$opt" = "--" ]; then
shift
EXT_ARGV="$@"
break
fi
if [ "$opt" = "--version" ]; then
version=$(grep '^pt-[^ ]\+ [0-9]' $0)
echo "$version"
exit 0
fi
if [ "$opt" = "--help" ]; then
usage $file
exit 0
fi
shift
if [ $(expr "$opt" : "-") -eq 0 ]; then
ARGV[i]="$opt"
i=$((i+1))
continue
fi
opt=$(echo $opt | sed 's/^-*//')
spec=$(grep -E "^$opt,|,$opt," "$TMPDIR/options")
if [ -z "$spec" ]; then
die "Unknown option: $opt"
fi
opt=$(echo $spec | cut -d',' -f1)
required_arg=$(echo $spec | cut -d',' -f3)
val="yes"
if [ -n "$required_arg" ]; then
if [ $# -eq 0 ]; then
die "--$opt requires a $required_arg argument"
else
val="$1"
shift
fi
fi
opt=$(echo $opt | sed 's/-/_/g' | tr [:lower:] [:upper:])
eval "OPT_${opt}"="$val"
done
}
# ###########################################################################
# End parse_options package
# ###########################################################################
# ########################################################################### # ###########################################################################
# tmpdir package # tmpdir package
# This package is a copy without comments from the original. The original # This package is a copy without comments from the original. The original
@@ -43,96 +210,11 @@ rm_tmpdir() {
# End tmpdir package # End tmpdir package
# ########################################################################### # ###########################################################################
# ###########################################################################
# Subroutines
# ###########################################################################
set +u set +u
# ########################################################################
# Check for the existence of a config file and source it if it exists
# ########################################################################
if [ -f "${0}.conf" ]; then
. "${0}.conf"
fi
# ########################################################################
# Configuration settings.
# ########################################################################
# This is the max number of <whatever> we want to tolerate.
THRESHOLD=${THRESHOLD:-30}
# This is the thing to check for.
VARIABLE=${VARIABLE:-Threads_running}
# How many times must the condition be met before the script will fire?
CYCLES=${CYCLES:-1}
# Collect GDB stacktraces?
GDB=${GDB:-no}
# Collect oprofile data?
OPROFILE=${OPROFILE:-yes}
# Collect strace data?
STRACE=${STRACE:-no}
# Collect tcpdump data?
TCPDUMP=${TCPDUMP:-yes}
# Send mail to this list of addresses when the script triggers.
# EMAIL=
# Any options to pass to mysql/mysqladmin, such as -u, -p, etc
# MYSQLOPTIONS=""
# This is the interval between checks.
INTERVAL=${INTERVAL:-30}
# If the command you're running to detect the condition is allowed to return
# nothing (e.g. a grep line that might not even exist if there's no problem),
# then set this to "yes".
MAYBE_EMPTY=${MAYBE_EMPTY:-no}
# This is the location of the 'collect' script.
if [ -z "${COLLECT}" ]; then
COLLECT="${HOME}/bin/pt-collect";
fi
# This is where to store the collected data.
if [ -z "${DEST}" ]; then
DEST="${HOME}/collected/"
fi
# How long to collect statistics data for? Make sure that this isn't longer
# than SLEEP.
DURATION=${DURATION:-30}
# How long to sleep after collecting?
if [ -z "${SLEEP}" ]; then
SLEEP=$(($DURATION * 10))
fi
# Bail out if the disk is more than this %full.
PCT_THRESHOLD=${PCT_THRESHOLD:-95}
# Bail out if the disk has less than this many MB free.
MB_THRESHOLD=${MB_THRESHOLD:-100}
# Remove samples after this many days.
PURGE=${PURGE:-30}
# Which trigger function to call to get the value of VARIABLE.
TRIGGER_FUNCTION=${TRIGGER_FUNCTION:-"status"}
# ########################################################################
# End configuration
# ########################################################################
# ########################################################################
# Echo to STDERR and exit false.
# ########################################################################
die() {
echo "${1}" >&2
exit 1
}
grep_processlist() { grep_processlist() {
local file=$1 local file=$1
local col=$2 local col=$2
@@ -178,27 +260,24 @@ grep_processlist() {
} }
set_trg_func() { set_trg_func() {
if [ -f "$TRIGGER_FUNCTION" ]; then if [ -f "$OPT_TRIGGER_FUNCTION" ]; then
source $TRIGGER_FUNCTION source $OPT_TRIGGER_FUNCTION
TRIGGER_FUNCTION="trg_plugin" TRIGGER_FUNCTION="trg_plugin"
else else
TRIGGER_FUNCTION="trg_$TRIGGER_FUNCTION" TRIGGER_FUNCTION="trg_$OPT_TRIGGER_FUNCTION"
fi fi
} }
# ########################################################################
# Trigger functions
# ########################################################################
trg_status() { trg_status() {
local var=$1 local var=$1
mysqladmin ${MYSQLOPTIONS} extended-status | grep ${VARIABLE} | awk '{print $4}' mysqladmin "$EXT_ARGV" extended-status | grep "$OPT_VARIABLE" | awk '{print $4}'
} }
trg_processlist() { trg_processlist() {
local var=$1 local var=$1
local tmpfile="$TMPDIR/processlist" local tmpfile="$TMPDIR/processlist"
mysqladmin ${MYSQLOPTIONS} processlist > $tmpfile-1 mysqladmin "$EXT_ARGV" processlist > $tmpfile-1
grep_processlist $tmpfile-1 $var $MATCH 0 0 > $tmpfile-2 grep_processlist $tmpfile-1 $var $OPT_MATCH 0 0 > $tmpfile-2
wc -l $tmpfile-2 | awk '{print $1}' wc -l $tmpfile-2 | awk '{print $1}'
rm -rf $tmpfile* rm -rf $tmpfile*
return return
@@ -209,51 +288,45 @@ trg_magic() {
return return
} }
# ######################################################################## # ###########################################################################
# Echo to STDERR and possibly email. # Main program loop, called below if tool is ran from the command line.
# ######################################################################## # ###########################################################################
log() {
if [ "${EMAIL}" ]; then
echo "${1} on $(hostname)" | mail -s "${2} on $(hostname)" ${EMAIL}
fi
echo "${1}" >&2
}
# The main code that runs by default. Arguments are the command-line options.
main() { main() {
mk_tmpdir
parse_options $0 "$@"
# Make the collection location # Make the collection location
mkdir -p "${DEST}" || die "Can't make the destination directory" # mkdir -p "$OPT_DEST" || die "Can't make the destination directory"
test -d "${DEST}" || die "${DEST} isn't a directory" # test -d "$OPT_DEST" || die "$OPT_DEST isn't a directory"
test -w "${DEST}" || die "${DEST} isn't writable" # test -w "$OPT_DEST" || die "$OPT_DEST isn't writable"
# Test if we have root; warn if not, but it isn't critical. # Test if we have root; warn if not, but it isn't critical.
if [ "$(id -u)" != "0" ]; then if [ "$(id -u)" != "0" ]; then
echo 'Not running with root privileges!'; echo 'Not running with root privileges!';
fi fi
# Make a secure tmpdir. Any output should be saved only in $TMPDIR/.
mk_tmpdir
# We increment this variable every time that the check is true, # We increment this variable every time that the check is true,
# and set it to 0 if it's false. # and set it to 0 if it's false.
cycles_true=0; local cycles_true=0
local matched="no"
set_trg_func set_trg_func
while true; do while true; do
d=$(date +%F-%T | tr :- _);
# This is where we decide whether to execute 'collect'. # This is where we decide whether to execute 'collect'.
# The idea is to generate a number and store into $detected, # The idea is to generate a number and store into $detected,
# and if $detected > $THRESHOLD, then we'll execute pt-collect. # and if $detected > $OPT_THRESHOLD, then we'll execute pt-collect.
local detected=$("${TRIGGER_FUNCTION}" $VARIABLE) local value=$($TRIGGER_FUNCTION $OPT_VARIABLE)
local trg_exit_status=$?
if [ -z "${detected}" -a ${MAYBE_EMPTY} = "no" ]; then if [ -z "$value" ]; then
# Oops, couldn't connect, maybe max_connections problem? # No value. Maybe we failed to connect to MySQL?
echo "$d The detected value is empty; something failed? Exit status is $?" warn "Detected value is empty; something failed? Trigger exit status: $trg_exit_status"
matched="yes" matched="no"
cycles_true=$(($cycles_true + 1)) cycles_true=0
elif [ "${detected:-0}" -gt ${THRESHOLD} ]; then elif [ $value -gt $OPT_THRESHOLD ]; then
matched="yes" matched="yes"
cycles_true=$(($cycles_true + 1)) cycles_true=$(($cycles_true + 1))
else else
@@ -261,25 +334,36 @@ main() {
cycles_true=0 cycles_true=0
fi fi
NOTE="$d check results: ${VARIABLE} = ${detected}, matched = ${matched}, cycles_true = ${cycles_true}" log "Check results: $OPT_VARIABLE=$value, matched=$matched, cycles_true=$cycles_true"
# Actually execute the collection script.
if [ "${matched:-no}" = "yes" -a ${cycles_true} -ge ${CYCLES} ]; then
log "${NOTE}" "${COLLECT} triggered" if [ "$matched" = "yes" -a $cycles_true -ge $OPT_CYCLES ]; then
PREFIX="$(date +%F-%T | tr :- _)" log "$OPT_COLLECT triggered"
echo "${NOTE}" > "${DEST}/${PREFIX}-trigger"
${COLLECT} -d "${DEST}" -i "${DURATION}" -g "${GDB}" -o "${OPROFILE}" -p "${PREFIX}" -s "${STRACE}" -t "${TCPDUMP}" -f "${PCT_THRESHOLD}" -m "${MB_THRESHOLD}" -- ${MYSQLOPTIONS} # PREFIX="$(date +%F-%T | tr :- _)"
echo "$d sleeping ${SLEEP} seconds to avoid DOS attack" # echo "${NOTE}" > "${DEST}/${PREFIX}-trigger"
sleep ${SLEEP}
# Run pt-collect.
$OPT_COLLECT \
-i "$OPT_RUN_TIME" \
-g "$OPT_GDB" \
-o "$OPT_OPROFILE" \
-p "$OPT_PREFIX" \
-s "$OPT_STRACE" \
-t "$OPT_TCPDUMP" \
-f "$OPT_PCT_THRESHOLD" \
-m "$OPT_MB_THRESHOLD" \
-- "$EXT_ARGV"
log "Sleeping $OPT_SLEEP seconds to avoid DOS attack"
sleep $OPT_SLEEP
else else
echo ${NOTE} sleep $OPT_INTERVAL
sleep ${INTERVAL}
fi fi
# Delete things more than $PURGE days old # Delete things more than $PURGE days old
find "${DEST}" -type f -mtime +${PURGE} -exec rm -f '{}' \; #find "$OPT_DEST" -type f -mtime +$OPT_PURGE -exec rm -f '{}' \;
find "/var/lib/oprofile/samples" -type d -name 'pt_collect_*' \ #find "/var/lib/oprofile/samples" -type d -name 'pt_collect_*' \
-depth -mtime +${PURGE} -exec rm -f '{}' \; # -depth -mtime +$OPT_PURGE -exec rm -f '{}' \;
done done
@@ -294,6 +378,8 @@ if [ "$(basename "$0")" = "pt-stalk" ] || [ "$(basename "$0")" = "bash" -a "$_"
main "$@" main "$@"
fi fi
exit $EXIT_STATUS
# ############################################################################ # ############################################################################
# Documentation # Documentation
# ############################################################################ # ############################################################################
@@ -306,7 +392,7 @@ pt-stalk - Wait for a condition to occur then begin collecting data.
=head1 SYNOPSIS =head1 SYNOPSIS
Usage: pt-stalk Usage: pt-stalk [OPTIONS] [-- MYSQL OPTIONS]
pt-stalk watches for a condition to become true, and when it does, executes pt-stalk watches for a condition to become true, and when it does, executes
a script. By default it executes L<pt-collect>, but that can be customized. a script. By default it executes L<pt-collect>, but that can be customized.
@@ -357,7 +443,7 @@ is too full.
By default, the tool is configured to execute mysqladmin extended-status and By default, the tool is configured to execute mysqladmin extended-status and
extract the value of the Threads_running variable; if this is greater than extract the value of the Threads_running variable; if this is greater than
30, it runs the collection script. This is really just placeholder code, 25, it runs the collection script. This is really just placeholder code,
and almost certainly needs to be customized! and almost certainly needs to be customized!
If the tool does execute the collection script, it will wait for a while If the tool does execute the collection script, it will wait for a while
@@ -368,39 +454,83 @@ The name 'stalk' is because 'watch' is already taken, and 'stalk' is fun.
=head1 CONFIGURING =head1 CONFIGURING
If the file F<pt-stalk.conf> exists in the current working directory, then TODO
L<"ENVIRONMENT"> variables are imported from it. For example, the config
file has the format:
INTERVAL=10
GDB=yes
See L<"ENVIRONMENT">.
=head1 OPTIONS =head1 OPTIONS
This tool does not have any command-line options, but see
L<"ENVIRONMENT"> and L<"CONFIGURING">.
=head1 ENVIRONMENT
The following environment variables configure how, what, and when the tool
runs. They are all optional and can be specified either on the command line
or in the F<pt-stalk.conf> config file (see L<"CONFIGURING">).
=over =over
=item THRESHOLD (default 30) =item --collect DIRECTORY
This is the max number of <whatever> we want to tolerate. Location of the C<collect> tool. (default: ${HOME}/bin/pt-collect)
=item VARIABLE (default Threads_running) =item --cycles N
This is the thing to check for. Number of times condition must be met before triggering collection. (default: 5)
=item TRIGGER_FUNCTION (default status) =item --dest DIRECTORY
Built-in function name or plugin file name which returns the value of C<VARIABLE>. Possible values are: Where to store collected data.
=item --disk-byte-limit MEGABYTES
Exit if the disk has less than this many MB free. (default: 100)
=item --disk-pct-limit PERCENT
Exit if the disk is less than this %full. (default: 5)
=item --email ADDRESS
Send mail to this list of addresses when the script triggers.
=item --gdb
Collect GDB stacktraces.
=item --help
Print help and exit.
=item --interval SECONDS
Interval between checks. (default: 1)
=item --match PATTERN
Match pattern for C<processles> L<"--trigger-function">.
=item --oprofile
Collect oprofile data. (default: yes)
=item --purge DAYS
Remove samples after this many days. (default: 30)
=item --run-time SECONDS
How long to collect statistics data for? (default: 30)
Make sure that this isn't longer than SLEEP.
=item --strace
Collect strace data.
=item --sleep SECONDS
How long to sleep after collecting? (default: 300)
=item --tcpdump
Collect tcpdump data? (default: yes)
=item --trigger-function
Built-in function name or plugin file name which returns the value of C<VARIABLE>. (default: status)
Possible values are:
=over =over
@@ -444,75 +574,24 @@ global variables with "PLUGIN_".
=back =back
=item CYCLES (default 1) =item --threshold N
How many times must the condition be met before the script will fire? Max number of C<N> to tolerate. (default: 25)
=item GDB (default no) =item --variable NAME
Collect GDB stacktraces? This is the thing to check for. (default: Threads_running)
=item OPROFILE (default yes) =item --version
Collect oprofile data? Print tool's version and exit.
=item STRACE (default no)
Collect strace data?
=item TCPDUMP (default yes)
Collect tcpdump data?
=item EMAIL
Send mail to this list of addresses when the script triggers.
=item MYSQLOPTIONS
Any options to pass to mysql/mysqladmin, such as -u, -p, etc
=item INTERVAL (default 30)
This is the interval between checks.
=item MAYBE_EMPTY (default no)
If the command you're running to detect the condition is allowed to return
nothing (e.g. a grep line that might not even exist if there's no problem),
then set this to "yes".
=item COLLECT (default ${HOME}/bin/pt-collect)
This is the location of the 'collect' script.
=item DEST (default ${HOME}/collected/)
This is where to store the collected data.
=item DURATION (default 30)
How long to collect statistics data for? Make sure that this isn't longer
than SLEEP.
=item SLEEP (default DURATION * 10)
How long to sleep after collecting?
=item PCT_THRESHOLD (default 95)
Bail out if the disk is more than this %full.
=item MB_THRESHOLD (default 100)
Bail out if the disk has less than this many MB free.
=item PURGE (default 30)
Remove samples after this many days.
=back =back
=head1 ENVIRONMENT
No env vars used.
=head1 SYSTEM REQUIREMENTS =head1 SYSTEM REQUIREMENTS
This tool requires Bash v3 or newer. This tool requires Bash v3 or newer.