mirror of
https://github.com/percona/percona-toolkit.git
synced 2025-09-25 13:46:22 +00:00
merge changes back from Daniel
This commit is contained in:
575
bin/pt-stalk
575
bin/pt-stalk
@@ -49,48 +49,83 @@ die() {
|
||||
# ###########################################################################
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
set -u
|
||||
|
||||
ARGV="" # Non-option args (probably input files)
|
||||
EXT_ARGV="" # Everything after -- (args for an external command)
|
||||
HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
|
||||
OPT_ERRS=0 # How many command line option errors
|
||||
OPT_VERSION="no" # If --version was specified
|
||||
OPT_HELP="no" # If --help was specified
|
||||
OPT_VERSION="" # If --version was specified
|
||||
OPT_HELP="" # If --help was specified
|
||||
PO_DIR="$TMPDIR/po" # Directory with program option spec files
|
||||
|
||||
usage() {
|
||||
local file="$1"
|
||||
|
||||
local usage=$(grep '^Usage: ' "$file")
|
||||
echo $usage >&2
|
||||
echo >&2
|
||||
echo "For more information, 'man $TOOL' or 'perldoc $file'." >&2
|
||||
echo $usage
|
||||
echo
|
||||
echo "For more information, 'man $TOOL' or 'perldoc $file'."
|
||||
}
|
||||
|
||||
usage_or_errors() {
|
||||
local file="$1"
|
||||
|
||||
if [ "$OPT_VERSION" = "yes" ]; then
|
||||
if [ "$OPT_VERSION" ]; then
|
||||
local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
|
||||
echo "$version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$OPT_HELP" = "yes" ]; then
|
||||
if [ "$OPT_HELP" ]; then
|
||||
usage "$file"
|
||||
echo >&2
|
||||
echo "Command line options:" >&2
|
||||
echo >&2
|
||||
for opt in $(ls $TMPDIR/po/); do
|
||||
local desc=$(cat $TMPDIR/po/$opt | grep '^desc:' | sed -e 's/^desc://')
|
||||
echo "--$opt" >&2
|
||||
echo " $desc" >&2
|
||||
echo >&2
|
||||
echo
|
||||
echo "Command line options:"
|
||||
echo
|
||||
perl -e '
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
my $lcol = 20; # Allow this much space for option names.
|
||||
my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
|
||||
my $name;
|
||||
while ( <> ) {
|
||||
my $line = $_;
|
||||
chomp $line;
|
||||
if ( $line =~ s/^long:/ --/ ) {
|
||||
$name = $line;
|
||||
}
|
||||
elsif ( $line =~ s/^desc:// ) {
|
||||
$line =~ s/ +$//mg;
|
||||
my @lines = grep { $_ }
|
||||
$line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
|
||||
if ( length($name) >= $lcol ) {
|
||||
print $name, "\n", (q{ } x $lcol);
|
||||
}
|
||||
else {
|
||||
printf "%-${lcol}s", $name;
|
||||
}
|
||||
print join("\n" . (q{ } x $lcol), @lines);
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
' "$PO_DIR"/*
|
||||
echo
|
||||
echo "Options and values after processing arguments:"
|
||||
echo
|
||||
for opt in $(ls "$PO_DIR"); do
|
||||
local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
|
||||
local varvalue="${!varname}"
|
||||
printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
|
||||
echo
|
||||
done
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ $OPT_ERRS -gt 0 ]; then
|
||||
echo >&2
|
||||
echo
|
||||
usage "$file"
|
||||
return 1
|
||||
fi
|
||||
@@ -102,60 +137,88 @@ parse_options() {
|
||||
local file="$1"
|
||||
shift
|
||||
|
||||
if [ ! -d "$TMPDIR/po/" ]; then
|
||||
mkdir "$TMPDIR/po/"
|
||||
ARGV=""
|
||||
EXT_ARGV=""
|
||||
HAVE_EXT_ARGV=""
|
||||
OPT_ERRS=0
|
||||
OPT_VERSION=""
|
||||
OPT_HELP=""
|
||||
PO_DIR="$TMPDIR/po"
|
||||
|
||||
if [ ! -d "$PO_DIR" ]; then
|
||||
mkdir "$PO_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Cannot mkdir $TMPDIR/po/" >&2
|
||||
echo "Cannot mkdir $PO_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$TMPDIR"/po/*
|
||||
rm -rf "$PO_DIR"/*
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Cannot rm -rf $TMPDIR/po/*" >&2
|
||||
echo "Cannot rm -rf $PO_DIR/*" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(
|
||||
export PO_DIR="$TMPDIR/po"
|
||||
cat "$file" | perl -ne '
|
||||
BEGIN { $/ = ""; }
|
||||
next unless $_ =~ m/^=head1 OPTIONS/;
|
||||
while ( defined(my $para = <>) ) {
|
||||
last if $para =~ m/^=head1/;
|
||||
|
||||
_parse_pod "$file" # Parse POD into program option (po) spec files
|
||||
_eval_po # Eval po into existence with default values
|
||||
|
||||
if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
|
||||
shift # --config
|
||||
local user_config_files="$1"
|
||||
shift # that ^
|
||||
local IFS=","
|
||||
for user_config_file in $user_config_files; do
|
||||
_parse_config_files "$user_config_file"
|
||||
done
|
||||
else
|
||||
_parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
|
||||
fi
|
||||
|
||||
_parse_command_line "$@"
|
||||
}
|
||||
|
||||
_parse_pod() {
|
||||
local file="$1"
|
||||
|
||||
cat "$file" | PO_DIR="$PO_DIR" perl -ne '
|
||||
BEGIN { $/ = ""; }
|
||||
next unless $_ =~ m/^=head1 OPTIONS/;
|
||||
while ( defined(my $para = <>) ) {
|
||||
last if $para =~ m/^=head1/;
|
||||
chomp;
|
||||
if ( $para =~ m/^=item --(\S+)/ ) {
|
||||
my $opt = $1;
|
||||
my $file = "$ENV{PO_DIR}/$opt";
|
||||
open my $opt_fh, ">", $file or die "Cannot open $file: $!";
|
||||
print $opt_fh "long:$opt\n";
|
||||
$para = <>;
|
||||
chomp;
|
||||
if ( $para =~ m/^=item --(\S+)/ ) {
|
||||
my $opt = $1;
|
||||
my $file = "$ENV{PO_DIR}/$opt";
|
||||
open my $opt_fh, ">", $file or die "Cannot open $file: $!";
|
||||
print $opt_fh "long:$opt\n";
|
||||
if ( $para =~ m/^[a-z ]+:/ ) {
|
||||
map {
|
||||
chomp;
|
||||
my ($attrib, $val) = split(/: /, $_);
|
||||
print $opt_fh "$attrib:$val\n";
|
||||
} split(/; /, $para);
|
||||
$para = <>;
|
||||
chomp;
|
||||
if ( $para =~ m/^[a-z ]+:/ ) {
|
||||
map {
|
||||
chomp;
|
||||
my ($attrib, $val) = split(/: /, $_);
|
||||
print $opt_fh "$attrib:$val\n";
|
||||
} split(/; /, $para);
|
||||
$para = <>;
|
||||
chomp;
|
||||
}
|
||||
my ($desc) = $para =~ m/^([^?.]+)/;
|
||||
print $opt_fh "desc:$desc.\n";
|
||||
close $opt_fh;
|
||||
}
|
||||
my ($desc) = $para =~ m/^([^?.]+)/;
|
||||
print $opt_fh "desc:$desc.\n";
|
||||
close $opt_fh;
|
||||
}
|
||||
last;
|
||||
'
|
||||
)
|
||||
}
|
||||
last;
|
||||
'
|
||||
}
|
||||
|
||||
for opt_spec in $(ls "$TMPDIR/po/"); do
|
||||
_eval_po() {
|
||||
local IFS=":"
|
||||
for opt_spec in "$PO_DIR"/*; do
|
||||
local opt=""
|
||||
local default_val=""
|
||||
local neg=0
|
||||
while read line; do
|
||||
local key=$(echo $line | cut -d ':' -f 1)
|
||||
local val=$(echo $line | cut -d ':' -f 2)
|
||||
local size=0
|
||||
while read key val; do
|
||||
case "$key" in
|
||||
long)
|
||||
opt=$(echo $val | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
@@ -166,6 +229,7 @@ parse_options() {
|
||||
"short form")
|
||||
;;
|
||||
type)
|
||||
[ "$val" = "size" ] && size=1
|
||||
;;
|
||||
desc)
|
||||
;;
|
||||
@@ -175,13 +239,13 @@ parse_options() {
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Invalid attribute in $TMPDIR/po/$opt_spec: $line" >&2
|
||||
echo "Invalid attribute in $opt_spec: $line" >&2
|
||||
exit 1
|
||||
esac
|
||||
done < "$TMPDIR/po/$opt_spec"
|
||||
done < "$opt_spec"
|
||||
|
||||
if [ -z "$opt" ]; then
|
||||
echo "No long attribute in option spec $TMPDIR/po/$opt_spec" >&2
|
||||
echo "No long attribute in option spec $opt_spec" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -192,74 +256,157 @@ parse_options() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $size -eq 1 -a -n "$default_val" ]; then
|
||||
default_val=$(size_to_bytes $default_val)
|
||||
fi
|
||||
|
||||
eval "OPT_${opt}"="$default_val"
|
||||
done
|
||||
}
|
||||
|
||||
for opt; do
|
||||
if [ $# -eq 0 ]; then
|
||||
break # no more opts
|
||||
_parse_config_files() {
|
||||
|
||||
for config_file in "$@"; do
|
||||
test -f "$config_file" || continue
|
||||
|
||||
while read config_opt; do
|
||||
|
||||
echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
|
||||
|
||||
config_opt="$(echo "$config_opt" | sed -e 's/^[ ]*//' -e 's/[ ]*\$//' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
|
||||
|
||||
[ "$config_opt" = "" ] && continue
|
||||
|
||||
if ! [ "$HAVE_EXT_ARGV" ]; then
|
||||
config_opt="--$config_opt"
|
||||
fi
|
||||
|
||||
_parse_command_line "$config_opt"
|
||||
|
||||
done < "$config_file"
|
||||
|
||||
HAVE_EXT_ARGV="" # reset for each file
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
_parse_command_line() {
|
||||
local opt=""
|
||||
local val=""
|
||||
local next_opt_is_val=""
|
||||
local opt_is_ok=""
|
||||
local opt_is_negated=""
|
||||
local real_opt=""
|
||||
local required_arg=""
|
||||
local spec=""
|
||||
|
||||
for opt in "$@"; do
|
||||
if [ "$opt" = "--" -o "$opt" = "----" ]; then
|
||||
HAVE_EXT_ARGV=1
|
||||
continue
|
||||
fi
|
||||
opt=$1
|
||||
if [ "$opt" = "--" ]; then
|
||||
shift
|
||||
EXT_ARGV="$@"
|
||||
break
|
||||
fi
|
||||
shift
|
||||
if [ $(expr "$opt" : "-") -eq 0 ]; then
|
||||
if [ -z "$ARGV" ]; then
|
||||
ARGV="$opt"
|
||||
if [ "$HAVE_EXT_ARGV" ]; then
|
||||
if [ "$EXT_ARGV" ]; then
|
||||
EXT_ARGV="$EXT_ARGV $opt"
|
||||
else
|
||||
ARGV="$ARGV $opt"
|
||||
EXT_ARGV="$opt"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
local real_opt="$opt"
|
||||
|
||||
if $(echo $opt | grep -q '^--no-'); then
|
||||
neg=1
|
||||
opt=$(echo $opt | sed 's/^--no-//')
|
||||
else
|
||||
neg=0
|
||||
opt=$(echo $opt | sed 's/^-*//')
|
||||
fi
|
||||
|
||||
if [ -f "$TMPDIR/po/$opt" ]; then
|
||||
spec="$TMPDIR/po/$opt"
|
||||
else
|
||||
spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
|
||||
if [ -z "$spec" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Unknown option: $real_opt" >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
local required_arg=$(cat $spec | awk -F: '/^type:/{print $2}')
|
||||
if [ -n "$required_arg" ]; then
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ "$next_opt_is_val" ]; then
|
||||
next_opt_is_val=""
|
||||
if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "$real_opt requires a $required_arg argument" >&2
|
||||
continue
|
||||
else
|
||||
val="$1"
|
||||
shift
|
||||
fi
|
||||
val="$opt"
|
||||
opt_is_ok=1
|
||||
else
|
||||
if [ $neg -eq 0 ]; then
|
||||
val="yes"
|
||||
if [ $(expr "$opt" : "-") -eq 0 ]; then
|
||||
if [ -z "$ARGV" ]; then
|
||||
ARGV="$opt"
|
||||
else
|
||||
ARGV="$ARGV $opt"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
real_opt="$opt"
|
||||
|
||||
if $(echo $opt | grep '^--no-' >/dev/null); then
|
||||
opt_is_negated=1
|
||||
opt=$(echo $opt | sed 's/^--no-//')
|
||||
else
|
||||
val="no"
|
||||
opt_is_negated=""
|
||||
opt=$(echo $opt | sed 's/^-*//')
|
||||
fi
|
||||
|
||||
if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
|
||||
val="$(echo $opt | awk -F= '{print $2}')"
|
||||
opt="$(echo $opt | awk -F= '{print $1}')"
|
||||
fi
|
||||
|
||||
if [ -f "$TMPDIR/po/$opt" ]; then
|
||||
spec="$TMPDIR/po/$opt"
|
||||
else
|
||||
spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
|
||||
if [ -z "$spec" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Unknown option: $real_opt" >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
|
||||
if [ "$required_arg" ]; then
|
||||
if [ "$val" ]; then
|
||||
opt_is_ok=1
|
||||
else
|
||||
next_opt_is_val=1
|
||||
fi
|
||||
else
|
||||
if [ "$val" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Option $real_opt does not take a value" >&2
|
||||
continue
|
||||
fi
|
||||
if [ "$opt_is_negated" ]; then
|
||||
val=""
|
||||
else
|
||||
val="yes"
|
||||
fi
|
||||
opt_is_ok=1
|
||||
fi
|
||||
fi
|
||||
|
||||
opt=$(cat $spec | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
if [ "$opt_is_ok" ]; then
|
||||
opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
|
||||
eval "OPT_$opt"="$val"
|
||||
if grep "^type:size" "$spec" >/dev/null; then
|
||||
val=$(size_to_bytes $val)
|
||||
fi
|
||||
|
||||
eval "OPT_$opt"="'$val'"
|
||||
|
||||
opt=""
|
||||
val=""
|
||||
next_opt_is_val=""
|
||||
opt_is_ok=""
|
||||
opt_is_negated=""
|
||||
real_opt=""
|
||||
required_arg=""
|
||||
spec=""
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
size_to_bytes() {
|
||||
local size="$1"
|
||||
echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};'
|
||||
}
|
||||
|
||||
# ###########################################################################
|
||||
# End parse_options package
|
||||
# ###########################################################################
|
||||
@@ -345,33 +492,37 @@ disk_space() {
|
||||
|
||||
check_disk_space() {
|
||||
local file="$1"
|
||||
local mb="${2:-0}"
|
||||
local pc="${3:-0}"
|
||||
local mb_margin="${4:-0}"
|
||||
local min_free_bytes="${2:-0}"
|
||||
local min_free_pct="${3:-0}"
|
||||
local bytes_margin="${4:-0}"
|
||||
|
||||
local kb=$(($mb * 1024))
|
||||
local kb_margin=$(($mb_margin * 1024))
|
||||
local used_bytes=$(cat "$file" | awk '/^\//{print $3 * 1024}');
|
||||
local free_bytes=$(cat "$file" | awk '/^\//{print $4 * 1024}');
|
||||
local pct_used=$(cat "$file" | awk '/^\//{print $5}' | sed -e 's/%//g');
|
||||
local pct_free=$((100 - $pct_used))
|
||||
|
||||
local kb_used=$(cat "$file" | awk '/^\//{print $3}');
|
||||
local kb_free=$(cat "$file" | awk '/^\//{print $4}');
|
||||
local pc_used=$(cat "$file" | awk '/^\//{print $5}' | sed -e 's/%//g');
|
||||
local real_free_bytes=$free_bytes
|
||||
local real_pct_free=$pct_free
|
||||
|
||||
if [ "$kb_margin" -gt "0" ]; then
|
||||
local kb_total=$(($kb_used + $kb_free))
|
||||
if [ $bytes_margin -gt 0 ]; then
|
||||
used_bytes=$(($used_bytes + $bytes_margin))
|
||||
free_bytes=$(($free_bytes - $bytes_margin))
|
||||
pct_used=$(awk "BEGIN { printf(\"%d\", ($used_bytes/($used_bytes + $free_bytes)) * 100) }")
|
||||
|
||||
kb_used=$(($kb_used + $kb_margin))
|
||||
kb_free=$(($kb_free - $kb_margin))
|
||||
pc_used=$(awk "BEGIN { printf(\"%d\", $kb_used/$kb_total * 100) }")
|
||||
pct_free=$((100 - $pct_used))
|
||||
fi
|
||||
|
||||
local pc_free=$((100 - $pc_used))
|
||||
if [ $free_bytes -lt $min_free_bytes -o $pct_free -lt $min_free_pct ]; then
|
||||
warn "Not enough free disk space:
|
||||
Limit: ${min_free_pct}% free, ${min_free_bytes} bytes free
|
||||
Actual: ${real_pct_free}% free, ${real_free_bytes} bytes free (- $bytes_margin bytes margin)
|
||||
"
|
||||
cat "$file" >&2
|
||||
|
||||
if [ "$kb_free" -le "$kb" -o "$pc_free" -le "$pc" ]; then
|
||||
warn "Not enough free disk space: ${pc_free}% free, ${kb_free} KB free; wanted more than ${pc}% free or ${kb} KB free"
|
||||
return 1
|
||||
return 1 # not enough disk space
|
||||
fi
|
||||
|
||||
return 0
|
||||
return 0 # disk space is OK
|
||||
}
|
||||
|
||||
# ###########################################################################
|
||||
@@ -471,7 +622,7 @@ collect() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$CMD_GDB" -a "$OPT_COLLECT_GDB" = "yes" -a "$mysqld_pid" ]; then
|
||||
if [ "$CMD_GDB" -a "$OPT_COLLECT_GDB" -a "$mysqld_pid" ]; then
|
||||
$CMD_GDB \
|
||||
-ex "set pagination 0" \
|
||||
-ex "thread apply all bt" \
|
||||
@@ -510,7 +661,7 @@ collect() {
|
||||
open_tables >> "$d/$p-opentables1" 2>&1 &
|
||||
|
||||
local tcpdump_pid=""
|
||||
if [ "$CMD_TCPDUMP" -a "$OPT_COLLECT_TCPDUMP" = "yes" ]; then
|
||||
if [ "$CMD_TCPDUMP" -a "$OPT_COLLECT_TCPDUMP" ]; then
|
||||
local port=$(awk '/^port/{print $2}' "$d/$p-variables")
|
||||
if [ "$port" ]; then
|
||||
$CMD_TCPDUMP -i any -s 4096 -w "$d/$p-tcpdump" port ${port} &
|
||||
@@ -519,12 +670,12 @@ collect() {
|
||||
fi
|
||||
|
||||
local have_oprofile="no"
|
||||
if [ "$CMD_OPCONTROL" -a "$OPT_COLLECT_OPROFILE" = "yes" ]; then
|
||||
if [ "$CMD_OPCONTROL" -a "$OPT_COLLECT_OPROFILE" ]; then
|
||||
if $CMD_OPCONTROL --init; then
|
||||
$CMD_OPCONTROL --start --no-vmlinux
|
||||
have_oprofile="yes"
|
||||
fi
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" = "yes" ]; then
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" ]; then
|
||||
$CMD_STRACE -T -s 0 -f -p $mysqld_pid > "${DEST}/$d-strace" 2>&1 &
|
||||
local strace_pid=$!
|
||||
fi
|
||||
@@ -561,8 +712,8 @@ collect() {
|
||||
disk_space $d > $d/$p-disk-space
|
||||
check_disk_space \
|
||||
$d/$p-disk-space \
|
||||
"$OPT_DISK_BYTE_LIMIT" \
|
||||
"$OPT_DISK_PCT_LIMIT" \
|
||||
"$OPT_DISK_BYTES_FREE" \
|
||||
"$OPT_DISK_PCT_FREE" \
|
||||
|| break
|
||||
|
||||
sleep $(date +%s.%N | awk '{print 1 - ($1 % 1)}')
|
||||
@@ -623,7 +774,7 @@ collect() {
|
||||
"/path/to/mysqld'" \
|
||||
> "$d/$p-opreport"
|
||||
fi
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" = "yes" ]; then
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" ]; then
|
||||
kill -s 2 $strace_pid
|
||||
sleep 1
|
||||
kill -s 15 $strace_pid
|
||||
@@ -770,11 +921,6 @@ trg_processlist() {
|
||||
return
|
||||
}
|
||||
|
||||
trg_magic() {
|
||||
echo "TODO"
|
||||
return
|
||||
}
|
||||
|
||||
oktorun() {
|
||||
if [ $OKTORUN -eq 0 ]; then
|
||||
EXIT_REASON="OKTORUN is false"
|
||||
@@ -828,7 +974,7 @@ sigtrap() {
|
||||
|
||||
stalk() {
|
||||
local cycles_true=0 # increment each time check is true, else set to 0
|
||||
local matched="no" # set to "yes" when check is true
|
||||
local matched="" # set to "yes" when check is true
|
||||
local last_prefix="" # prefix of last collection
|
||||
|
||||
while oktorun; do
|
||||
@@ -841,61 +987,65 @@ stalk() {
|
||||
if [ -z "$value" ]; then
|
||||
# No value. Maybe we failed to connect to MySQL?
|
||||
warn "Detected value is empty; something failed? Trigger exit status: $trg_exit_status"
|
||||
matched="no"
|
||||
matched=""
|
||||
cycles_true=0
|
||||
elif [ $value -gt $OPT_THRESHOLD ]; then
|
||||
matched="yes"
|
||||
cycles_true=$(($cycles_true + 1))
|
||||
else
|
||||
matched="no"
|
||||
matched=""
|
||||
cycles_true=0
|
||||
fi
|
||||
|
||||
local msg="Check results: $OPT_VARIABLE=$value, matched=$matched, cycles_true=$cycles_true"
|
||||
local msg="Check results: $OPT_VARIABLE=$value, matched=${matched:-no}, cycles_true=$cycles_true"
|
||||
log "$msg"
|
||||
|
||||
if [ "$matched" = "yes" -a $cycles_true -ge $OPT_CYCLES ]; then
|
||||
if [ "$matched" -a $cycles_true -ge $OPT_CYCLES ]; then
|
||||
# ##################################################################
|
||||
# Start collecting, maybe.
|
||||
# ##################################################################
|
||||
local prefix="${OPT_PREFIX:-$(date +%F-%T | tr :- _)}"
|
||||
log "Collect triggered"
|
||||
|
||||
# Check if we'll have enough disk space to collect. Disk space
|
||||
# is also checked every interval while collecting.
|
||||
local margin="20" # default 20M margin, unless:
|
||||
if [ -n "$last_prefix" ]; then
|
||||
margin=$(du -mc "$OPT_DEST"/"$last_prefix"-* | tail -n 1 | awk '{print $1'})
|
||||
fi
|
||||
disk_space "$OPT_DEST" > "$OPT_DEST/$prefix-disk-space"
|
||||
check_disk_space \
|
||||
"$OPT_DEST/$prefix-disk-space" \
|
||||
"$OPT_DISK_BYTE_LIMIT" \
|
||||
"$OPT_DISK_PCT_LIMIT" \
|
||||
"$margin" # real used MB + margin MB
|
||||
if [ $? -eq 0 ]; then
|
||||
# There should be enough disk space, so collect.
|
||||
log "$msg" >> "$OPT_DEST/$prefix-trigger"
|
||||
log "pt-stalk ran with $RAN_WITH" >> "$OPT_DEST/$prefix-trigger"
|
||||
last_prefix="$prefix"
|
||||
# Send email to whomever that collect has been triggered.
|
||||
if [ "$OPT_NOTIFY_BY_EMAIL" ]; then
|
||||
echo "$msg on $(hostname)" \
|
||||
| mail -s "Collect triggered on $(hostname)" \
|
||||
"$OPT_NOTIFY_BY_EMAIL"
|
||||
fi
|
||||
|
||||
# Send email to whomever that collect has been triggered.
|
||||
if [ "$OPT_NOTIFY_BY_EMAIL" ]; then
|
||||
echo "$msg on $(hostname)" \
|
||||
| mail -s "Collect triggered on $(hostname)" \
|
||||
"$OPT_NOTIFY_BY_EMAIL"
|
||||
if [ "$OPT_COLLECT" ]; then
|
||||
local prefix="${OPT_PREFIX:-$(date +%F-%T | tr :- _)}"
|
||||
|
||||
# Check if we'll have enough disk space to collect. Disk space
|
||||
# is also checked every interval while collecting.
|
||||
local margin="20971520" # default 20M margin, unless:
|
||||
if [ -n "$last_prefix" ]; then
|
||||
margin=$(du -mc "$OPT_DEST"/"$last_prefix"-* | tail -n 1 | awk '{print $1'})
|
||||
fi
|
||||
disk_space "$OPT_DEST" > "$OPT_DEST/$prefix-disk-space"
|
||||
check_disk_space \
|
||||
"$OPT_DEST/$prefix-disk-space" \
|
||||
"$OPT_DISK_BYTES_FREE" \
|
||||
"$OPT_DISK_PCT_FREE" \
|
||||
"$margin"
|
||||
if [ $? -eq 0 ]; then
|
||||
# There should be enough disk space, so collect.
|
||||
log "$msg" >> "$OPT_DEST/$prefix-trigger"
|
||||
log "pt-stalk ran with $RAN_WITH" >> "$OPT_DEST/$prefix-trigger"
|
||||
last_prefix="$prefix"
|
||||
|
||||
|
||||
# Fork and background the collect subroutine which will
|
||||
# run for --run-time seconds. We (the parent) sleep
|
||||
# while its collecting (hopefully --sleep is longer than
|
||||
# --run-time).
|
||||
(
|
||||
collect "$OPT_DEST" "$prefix"
|
||||
) >> "$OPT_DEST/$prefix-output" 2>&1 &
|
||||
else
|
||||
# There will not be enough disk space, so do not collect.
|
||||
warn "Collect canceled because there will not be enough disk space after collecting another $margin MB"
|
||||
fi
|
||||
|
||||
# Fork and background the collect subroutine which will
|
||||
# run for --run-time seconds. We (the parent) sleep
|
||||
# while its collecting (hopefully --sleep is longer than
|
||||
# --run-time).
|
||||
(
|
||||
collect "$OPT_DEST" "$prefix"
|
||||
) >> "$OPT_DEST/$prefix-output" 2>&1 &
|
||||
else
|
||||
# There will not be enough disk space, so do not collect.
|
||||
warn "Collect canceled because there will not be enough disk space after collecting another $margin MB"
|
||||
fi
|
||||
|
||||
# ##################################################################
|
||||
@@ -909,7 +1059,9 @@ stalk() {
|
||||
fi
|
||||
|
||||
# Purge old collect file between checks.
|
||||
purge_samples "$OPT_DEST" "$OPT_RETENTION_TIME"
|
||||
if [ -d "$OPT_DEST" ]; then
|
||||
purge_samples "$OPT_DEST" "$OPT_RETENTION_TIME"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
@@ -924,23 +1076,8 @@ main() {
|
||||
# Bash 4 has $BASHPID but we can't rely on that. Consequently,
|
||||
# we don't know our own PID. See the usage of $! below.
|
||||
RAN_WITH="--function=$OPT_FUNCTION --variable=$OPT_VARIABLE --threshold=$OPT_THRESHOLD --match=$OPT_MATCH --cycles=$OPT_CYCLES --interval=$OPT_INTERVAL --iterations=$OPT_ITERATIONS --run-time=$OPT_RUN_TIME --sleep=$OPT_SLEEP --dest=$OPT_DEST --prefix=$OPT_PREFIX --notify-by-email=$OPT_NOTIFY_BY_EMAIL --log=$OPT_LOG --pid=$OPT_PID"
|
||||
log "Starting $0 $RAN_WITH"
|
||||
|
||||
# Make sure the collection dir exists.
|
||||
if [ ! -d "$OPT_DEST" ]; then
|
||||
mkdir -p "$OPT_DEST" || die "Cannot make --dest $OPT_DEST"
|
||||
fi
|
||||
# Check access to the --dest dir. By setting -x in the subshell,
|
||||
# if either command fails, the subshell will exit immediately and
|
||||
# $? will be non-zero.
|
||||
(
|
||||
set -e
|
||||
touch "$OPT_DEST/test"
|
||||
rm "$OPT_DEST/test"
|
||||
)
|
||||
if [ $? -ne 0 ]; then
|
||||
die "Cannot read and write files to --dest $OPT_DEST"
|
||||
fi
|
||||
log "Starting $0 $RAN_WITH"
|
||||
|
||||
# Test if we have root; warn if not, but it isn't critical.
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
@@ -986,7 +1123,8 @@ if [ "$(basename "$0")" = "pt-stalk" ] \
|
||||
po_status=$?
|
||||
rm_tmpdir
|
||||
if [ $po_status -ne 0 ]; then
|
||||
exit $po_status
|
||||
[ $OPT_ERRS -gt 0 ] && exit 1
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Now that we have the cmd line opts, check that we can actually
|
||||
@@ -994,15 +1132,28 @@ if [ "$(basename "$0")" = "pt-stalk" ] \
|
||||
[ -n "$(mysql $EXT_ARGV -e 'SELECT 1')" ] \
|
||||
|| die "Cannot connect to MySQL. Check that MySQL is running and that the options after -- are correct."
|
||||
|
||||
if [ "$OPT_DAEMONIZE" = "yes" ]; then
|
||||
# Check access to the --log file.
|
||||
# Check existence and access to the --dest dir if we're collecting.
|
||||
if [ "$OPT_COLLECT" ]; then
|
||||
if [ ! -d "$OPT_DEST" ]; then
|
||||
mkdir -p "$OPT_DEST" || die "Cannot make --dest $OPT_DEST"
|
||||
fi
|
||||
|
||||
# Check access to the --dest dir. By setting -x in the subshell,
|
||||
# if either command fails, the subshell will exit immediately and
|
||||
# $? will be non-zero.
|
||||
(
|
||||
set -e
|
||||
touch "$OPT_LOG"
|
||||
touch "$OPT_DEST/test"
|
||||
rm "$OPT_DEST/test"
|
||||
)
|
||||
if [ $? -ne 0 ]; then
|
||||
die "Cannot write to --log $OPT_LOG"
|
||||
die "Cannot read and write files to --dest $OPT_DEST"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$OPT_DAEMONIZE" ]; then
|
||||
# Check access to the --log file.
|
||||
touch "$OPT_LOG" || die "Cannot write to --log $OPT_LOG"
|
||||
|
||||
# The PID file will at first have our (parent) PID.
|
||||
# This is fine for ensuring that only one of us is
|
||||
@@ -1173,6 +1324,13 @@ interfaces for the port on which MySQL is listening. You can later use
|
||||
pt-query-digest to decode the MySQL protocol and extract a log of query traffic
|
||||
from it.
|
||||
|
||||
=item --config
|
||||
|
||||
type: string
|
||||
|
||||
Read this comma-separated list of config files. If specified, this must be the
|
||||
first option on the command line.
|
||||
|
||||
=item --cycles
|
||||
|
||||
type: int; default: 5
|
||||
@@ -1188,34 +1346,39 @@ its output as specified in --log.
|
||||
|
||||
=item --dest
|
||||
|
||||
type: string; default: ${HOME}/collected
|
||||
type: string; default: /var/lib/pt-stalk
|
||||
|
||||
Where to store the diagnostic data. Each time the tool collects data, it writes
|
||||
to a new set of files, which are named with the current system timestamp.
|
||||
|
||||
=item --disk-byte-limit
|
||||
=item --disk-bytes-free
|
||||
|
||||
type: int; default: 100
|
||||
type: size; default: 100M
|
||||
|
||||
Don't collect data unless the destination disk has this much free space. This
|
||||
prevents the tool from filling up the disk with diagnostic data.
|
||||
Don't collect data if the disk has less than this much free space.
|
||||
This prevents the tool from filling up the disk with diagnostic data.
|
||||
|
||||
If the destination directory contains a previously captured sample of data, the
|
||||
tool will measure its size and use that as an estimate of how much data is
|
||||
If the L<"--dest"> directory contains a previously captured sample of data,
|
||||
the tool will measure its size and use that as an estimate of how much data is
|
||||
likely to be gathered this time, too. It will then be even more pessimistic,
|
||||
and will refuse to collect data unless the disk has enough free space to hold
|
||||
the sample and still have the desired amount of free space. For example, if
|
||||
you'd like 100MB of free space and the previous diagnostic sample consumed
|
||||
100MB, the tool won't collect any data unless the disk has 200MB free.
|
||||
|
||||
=item --disk-pct-limit
|
||||
Valid size value suffixes are k, M, G, and T.
|
||||
|
||||
=item --disk-pct-free
|
||||
|
||||
type: int; default: 5
|
||||
|
||||
Don't collect data unless the disk has at least this percent free space. This
|
||||
option works similarly to --disk-byte-limit, but specifies a percentage margin
|
||||
of safety instead of a byte margin of safety. The tool honors both options, and
|
||||
will not collect any data unless both margins are satisfied.
|
||||
Don't collect data if the disk has less than this percent free space.
|
||||
This prevents the tool from filling up the disk with diagnostic data.
|
||||
|
||||
This option works similarly to L<"--disk-bytes-free"> but specifies a
|
||||
percentage margin of safety instead of a bytes margin of safety.
|
||||
The tool honors both options, and will not collect any data unless both
|
||||
margins are satisfied.
|
||||
|
||||
=item --function
|
||||
|
||||
@@ -1271,8 +1434,6 @@ MySQL options mentioned in the L<"SYNOPSIS"> above.
|
||||
The plugin should not alter the tool's existing global variables. Prefix any
|
||||
plugin-specific global variables with "PLUGIN_" or make them local.
|
||||
|
||||
=back
|
||||
|
||||
=item --help
|
||||
|
||||
Print help and exit.
|
||||
|
@@ -182,8 +182,8 @@ The syntax of the configuration files is as follows:
|
||||
|
||||
=item *
|
||||
|
||||
Whitespace followed by a hash (#) sign signifies that the rest of the line is a
|
||||
comment. This is deleted.
|
||||
Whitespace followed by a hash sign (#) signifies that the rest of the line is a
|
||||
comment. This is deleted. For example:
|
||||
|
||||
=item *
|
||||
|
||||
@@ -200,7 +200,9 @@ Each line is permitted to be in either of the following formats:
|
||||
option
|
||||
option=value
|
||||
|
||||
Whitespace around the equals sign is deleted during processing.
|
||||
Do not prefix the option with C<-->. Do not quote the values, even if
|
||||
it has spaces; value are literal. Whitespace around the equals sign is
|
||||
deleted during processing.
|
||||
|
||||
=item *
|
||||
|
||||
@@ -214,6 +216,22 @@ program.
|
||||
|
||||
=back
|
||||
|
||||
=head2 EXAMPLE
|
||||
|
||||
This config file for pt-stalk,
|
||||
|
||||
# Config for pt-stalk
|
||||
variable=Threads_connected
|
||||
cycles=2 # trigger if problem seen twice in a row
|
||||
--
|
||||
--user daniel
|
||||
|
||||
is equivalent to this command line:
|
||||
|
||||
pt-stalk --variable Threads_connected --cycles 2 -- --user daniel
|
||||
|
||||
Options after C<--> are passed literally to mysql and mysqladmin.
|
||||
|
||||
=head2 READ ORDER
|
||||
|
||||
The tools read several configuration files in order:
|
||||
|
@@ -61,7 +61,7 @@ collect() {
|
||||
|
||||
# Getting a GDB stacktrace can be an intensive operation,
|
||||
# so do this only if necessary (and possible).
|
||||
if [ "$CMD_GDB" -a "$OPT_COLLECT_GDB" = "yes" -a "$mysqld_pid" ]; then
|
||||
if [ "$CMD_GDB" -a "$OPT_COLLECT_GDB" -a "$mysqld_pid" ]; then
|
||||
$CMD_GDB \
|
||||
-ex "set pagination 0" \
|
||||
-ex "thread apply all bt" \
|
||||
@@ -112,7 +112,7 @@ collect() {
|
||||
|
||||
# If TCP dumping is specified, start that on the server's port.
|
||||
local tcpdump_pid=""
|
||||
if [ "$CMD_TCPDUMP" -a "$OPT_COLLECT_TCPDUMP" = "yes" ]; then
|
||||
if [ "$CMD_TCPDUMP" -a "$OPT_COLLECT_TCPDUMP" ]; then
|
||||
local port=$(awk '/^port/{print $2}' "$d/$p-variables")
|
||||
if [ "$port" ]; then
|
||||
$CMD_TCPDUMP -i any -s 4096 -w "$d/$p-tcpdump" port ${port} &
|
||||
@@ -123,12 +123,12 @@ collect() {
|
||||
# Next, start oprofile gathering data during the whole rest of this process.
|
||||
# The --init should be a no-op if it has already been init-ed.
|
||||
local have_oprofile="no"
|
||||
if [ "$CMD_OPCONTROL" -a "$OPT_COLLECT_OPROFILE" = "yes" ]; then
|
||||
if [ "$CMD_OPCONTROL" -a "$OPT_COLLECT_OPROFILE" ]; then
|
||||
if $CMD_OPCONTROL --init; then
|
||||
$CMD_OPCONTROL --start --no-vmlinux
|
||||
have_oprofile="yes"
|
||||
fi
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" = "yes" ]; then
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" ]; then
|
||||
# Don't run oprofile and strace at the same time.
|
||||
$CMD_STRACE -T -s 0 -f -p $mysqld_pid > "${DEST}/$d-strace" 2>&1 &
|
||||
local strace_pid=$!
|
||||
@@ -178,8 +178,8 @@ collect() {
|
||||
disk_space $d > $d/$p-disk-space
|
||||
check_disk_space \
|
||||
$d/$p-disk-space \
|
||||
"$OPT_DISK_BYTE_LIMIT" \
|
||||
"$OPT_DISK_PCT_LIMIT" \
|
||||
"$OPT_DISK_BYTES_FREE" \
|
||||
"$OPT_DISK_PCT_FREE" \
|
||||
|| break
|
||||
|
||||
# Synchronize ourselves onto the clock tick, so the sleeps are 1-second
|
||||
@@ -244,7 +244,7 @@ collect() {
|
||||
"/path/to/mysqld'" \
|
||||
> "$d/$p-opreport"
|
||||
fi
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" = "yes" ]; then
|
||||
elif [ "$CMD_STRACE" -a "$OPT_COLLECT_STRACE" ]; then
|
||||
kill -s 2 $strace_pid
|
||||
sleep 1
|
||||
kill -s 15 $strace_pid
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This program is copyright 2011 Percona Inc.
|
||||
# This program is copyright 2011-2012 Percona Inc.
|
||||
# Feedback and improvements are welcome.
|
||||
#
|
||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
@@ -22,19 +22,38 @@
|
||||
# parse_options parses Perl POD options from Bash tools and creates
|
||||
# global variables for each option.
|
||||
|
||||
# ***********************************************************
|
||||
# XXX
|
||||
# GLOBAL $TMPDIR AND $TOOL MUST BE SET BEFORE USING THIS LIB!
|
||||
# ***********************************************************
|
||||
# XXX
|
||||
|
||||
# Parsing command line options with Bash is easy until we have to dealt
|
||||
# with values that have spaces, e.g. --option="hello world". This is
|
||||
# further complicated by command line vs. config file. From the command
|
||||
# line, <--option "hello world"> is put into $@ as "--option", "hello world",
|
||||
# i.e. 2 args. From a config file, <option=hello world> is either 2 args
|
||||
# split on the space, or 1 arg as a whole line. It needs to be 2 args
|
||||
# split on the = but this isn't possible; see the note before while read
|
||||
# in _parse_config_files(). Perl tool config files do not work when the
|
||||
# value is quoted, so we can't quote it either. And in any case, that
|
||||
# wouldn't work because then the value would include the literal quotes
|
||||
# because it's a line from a file, not a command line where Bash will
|
||||
# interpret the quotes and return a single value in the code. So...
|
||||
|
||||
# XXX
|
||||
# BE CAREFUL MAKING CHANGES TO THIS LIB AND MAKE SURE
|
||||
# t/lib/bash/parse_options.sh STILL PASSES!
|
||||
# XXX
|
||||
|
||||
set -u
|
||||
|
||||
# Global variables. These must be global because declare inside a
|
||||
# sub will be scoped locally.
|
||||
ARGV="" # Non-option args (probably input files)
|
||||
EXT_ARGV="" # Everything after -- (args for an external command)
|
||||
OPT_ERRS=0 # How many command line option errors
|
||||
OPT_VERSION="no" # If --version was specified
|
||||
OPT_HELP="no" # If --help was specified
|
||||
ARGV="" # Non-option args (probably input files)
|
||||
EXT_ARGV="" # Everything after -- (args for an external command)
|
||||
HAVE_EXT_ARGV="" # Got --, everything else is put into EXT_ARGV
|
||||
OPT_ERRS=0 # How many command line option errors
|
||||
OPT_VERSION="" # If --version was specified
|
||||
OPT_HELP="" # If --help was specified
|
||||
PO_DIR="$TMPDIR/po" # Directory with program option spec files
|
||||
|
||||
# Sub: usage
|
||||
@@ -61,21 +80,51 @@ usage() {
|
||||
usage_or_errors() {
|
||||
local file="$1"
|
||||
|
||||
if [ "$OPT_VERSION" = "yes" ]; then
|
||||
if [ "$OPT_VERSION" ]; then
|
||||
local version=$(grep '^pt-[^ ]\+ [0-9]' "$file")
|
||||
echo "$version"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ "$OPT_HELP" = "yes" ]; then
|
||||
if [ "$OPT_HELP" ]; then
|
||||
usage "$file"
|
||||
echo
|
||||
echo "Command line options:"
|
||||
echo
|
||||
for opt in $(ls $TMPDIR/po/); do
|
||||
local desc=$(cat $TMPDIR/po/$opt | grep '^desc:' | sed -e 's/^desc://')
|
||||
echo "--$opt"
|
||||
echo " $desc"
|
||||
perl -e '
|
||||
use strict;
|
||||
use warnings FATAL => qw(all);
|
||||
my $lcol = 20; # Allow this much space for option names.
|
||||
my $rcol = 80 - $lcol; # The terminal is assumed to be 80 chars wide.
|
||||
my $name;
|
||||
while ( <> ) {
|
||||
my $line = $_;
|
||||
chomp $line;
|
||||
if ( $line =~ s/^long:/ --/ ) {
|
||||
$name = $line;
|
||||
}
|
||||
elsif ( $line =~ s/^desc:// ) {
|
||||
$line =~ s/ +$//mg;
|
||||
my @lines = grep { $_ }
|
||||
$line =~ m/(.{0,$rcol})(?:\s+|\Z)/g;
|
||||
if ( length($name) >= $lcol ) {
|
||||
print $name, "\n", (q{ } x $lcol);
|
||||
}
|
||||
else {
|
||||
printf "%-${lcol}s", $name;
|
||||
}
|
||||
print join("\n" . (q{ } x $lcol), @lines);
|
||||
print "\n";
|
||||
}
|
||||
}
|
||||
' "$PO_DIR"/*
|
||||
echo
|
||||
echo "Options and values after processing arguments:"
|
||||
echo
|
||||
for opt in $(ls "$PO_DIR"); do
|
||||
local varname="OPT_$(echo "$opt" | tr a-z- A-Z_)"
|
||||
local varvalue="${!varname}"
|
||||
printf -- " --%-30s %s" "$opt" "${varvalue:-(No value)}"
|
||||
echo
|
||||
done
|
||||
return 1
|
||||
@@ -108,10 +157,18 @@ parse_options() {
|
||||
local file="$1"
|
||||
shift
|
||||
|
||||
# Change --op=val to --op val because _parse_command_line() needs
|
||||
# a space-separated list of "op val op val" etc.
|
||||
local opts=$(echo "$@" | perl -ne 's/--(\S+)=/--$1 /g, print')
|
||||
# XXX
|
||||
# Reset all globals else t/lib/bash/parse_options.sh will fail.
|
||||
# XXX
|
||||
ARGV=""
|
||||
EXT_ARGV=""
|
||||
HAVE_EXT_ARGV=""
|
||||
OPT_ERRS=0
|
||||
OPT_VERSION=""
|
||||
OPT_HELP=""
|
||||
PO_DIR="$TMPDIR/po"
|
||||
|
||||
# Ready the directory for the program option (po) spec files.
|
||||
if [ ! -d "$PO_DIR" ]; then
|
||||
mkdir "$PO_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
@@ -126,10 +183,25 @@ parse_options() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_parse_pod "$file"
|
||||
_eval_po
|
||||
_parse_config_files
|
||||
_parse_command_line $opts # do NOT quote, we want "--op" "val" not "--op val"
|
||||
_parse_pod "$file" # Parse POD into program option (po) spec files
|
||||
_eval_po # Eval po into existence with default values
|
||||
|
||||
# If the first option is --config FILES, then remove it and use
|
||||
# those files instead of the default config files.
|
||||
if [ $# -ge 2 ] && [ "$1" = "--config" ]; then
|
||||
shift # --config
|
||||
local user_config_files="$1"
|
||||
shift # that ^
|
||||
local IFS=","
|
||||
for user_config_file in $user_config_files; do
|
||||
_parse_config_files "$user_config_file"
|
||||
done
|
||||
else
|
||||
_parse_config_files "/etc/percona-toolkit/percona-toolkit.conf" "/etc/percona-toolkit/$TOOL.conf" "$HOME/.percona-toolkit.conf" "$HOME/.$TOOL.conf"
|
||||
fi
|
||||
|
||||
# Finally, parse the command line.
|
||||
_parse_command_line "$@"
|
||||
}
|
||||
|
||||
_parse_pod() {
|
||||
@@ -178,13 +250,13 @@ _eval_po() {
|
||||
# Evaluate the program options into existence as global variables
|
||||
# transformed like --my-op == $OPT_MY_OP. If an option has a default
|
||||
# value, it's assigned that value. Else, it's value is an empty string.
|
||||
for opt_spec in $(ls "$PO_DIR"); do
|
||||
local IFS=":"
|
||||
for opt_spec in "$PO_DIR"/*; do
|
||||
local opt=""
|
||||
local default_val=""
|
||||
local neg=0
|
||||
while read line; do
|
||||
local key=$(echo $line | cut -d ':' -f 1)
|
||||
local val=$(echo $line | cut -d ':' -f 2)
|
||||
local size=0
|
||||
while read key val; do
|
||||
case "$key" in
|
||||
long)
|
||||
opt=$(echo $val | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
@@ -195,6 +267,7 @@ _eval_po() {
|
||||
"short form")
|
||||
;;
|
||||
type)
|
||||
[ "$val" = "size" ] && size=1
|
||||
;;
|
||||
desc)
|
||||
;;
|
||||
@@ -204,13 +277,13 @@ _eval_po() {
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Invalid attribute in $PO_DIR/$opt_spec: $line" >&2
|
||||
echo "Invalid attribute in $opt_spec: $line" >&2
|
||||
exit 1
|
||||
esac
|
||||
done < "$PO_DIR/$opt_spec"
|
||||
done < "$opt_spec"
|
||||
|
||||
if [ -z "$opt" ]; then
|
||||
echo "No long attribute in option spec $PO_DIR/$opt_spec" >&2
|
||||
echo "No long attribute in option spec $opt_spec" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -221,25 +294,54 @@ _eval_po() {
|
||||
fi
|
||||
fi
|
||||
|
||||
# Convert sizes.
|
||||
if [ $size -eq 1 -a -n "$default_val" ]; then
|
||||
default_val=$(size_to_bytes $default_val)
|
||||
fi
|
||||
|
||||
# Eval the option into existence as a global variable.
|
||||
eval "OPT_${opt}"="$default_val"
|
||||
done
|
||||
}
|
||||
|
||||
_parse_config_files() {
|
||||
local config_files="/etc/percona-toolkit/percona-toolkit.conf /etc/percona-toolkit/$TOOL.conf $HOME/.percona-toolkit.conf $HOME/.$TOOL.conf"
|
||||
for config_file in $config_files; do
|
||||
|
||||
for config_file in "$@"; do
|
||||
# Next config file if this one doesn't exist.
|
||||
test -f "$config_file" || continue
|
||||
|
||||
# The config file syntax is just like a command line except there
|
||||
# is one option per line. In Bash, --foo --bar is the same as
|
||||
# --foo
|
||||
# --bar
|
||||
# So we can simply cat the config file into/as the command line.
|
||||
# The Perl changes --foo=bar to --foo bar because _parse_command_line()
|
||||
# needs a space-separated list of "opt val opt val" etc.
|
||||
_parse_command_line \
|
||||
$(cat "$config_file" | perl -ne 's/--(\S+)=/--$1 /g, print')
|
||||
# We must use while read because values can contain spaces.
|
||||
# Else, if we for $(grep ...) then a line like "op=hello world"
|
||||
# will return 2 values: "op=hello" and "world". If we quote
|
||||
# the command like for "$(grep ...)" then the entire config
|
||||
# file is returned as 1 value like "opt=hello world\nop2=42".
|
||||
while read config_opt; do
|
||||
|
||||
# Skip the line if it begins with a # or is blank.
|
||||
echo "$config_opt" | grep '^[ ]*[^#]' >/dev/null 2>&1 || continue
|
||||
|
||||
# Strip leading and trailing spaces, and spaces around the first =,
|
||||
# and end-of-line # comments.
|
||||
config_opt="$(echo "$config_opt" | sed -e 's/^[ ]*//' -e 's/[ ]*\$//' -e 's/[ ]*=[ ]*/=/' -e 's/[ ]*#.*$//')"
|
||||
|
||||
# Skip blank lines.
|
||||
[ "$config_opt" = "" ] && continue
|
||||
|
||||
# Options in a config file are not prefixed with --,
|
||||
# but command line options are, so one or the other has
|
||||
# to add or remove the -- prefix. We add it for config
|
||||
# files rather than trying to strip it from command line
|
||||
# options because it's a simpler operation here.
|
||||
if ! [ "$HAVE_EXT_ARGV" ]; then
|
||||
config_opt="--$config_opt"
|
||||
fi
|
||||
|
||||
_parse_command_line "$config_opt"
|
||||
|
||||
done < "$config_file"
|
||||
|
||||
HAVE_EXT_ARGV="" # reset for each file
|
||||
|
||||
done
|
||||
}
|
||||
|
||||
@@ -254,82 +356,138 @@ _parse_command_line() {
|
||||
# a default value 100, then $OPT_FOO=100 already, but if --foo=500 is
|
||||
# specified on the command line, then we re-eval $OPT_FOO=500 to update
|
||||
# $OPT_FOO.
|
||||
local opt=""
|
||||
local val=""
|
||||
local next_opt_is_val=""
|
||||
local opt_is_ok=""
|
||||
local opt_is_negated=""
|
||||
local real_opt=""
|
||||
local required_arg=""
|
||||
local spec=""
|
||||
|
||||
for opt in "$@"; do
|
||||
if [ $# -eq 0 ]; then
|
||||
break # no more opts
|
||||
if [ "$opt" = "--" -o "$opt" = "----" ]; then
|
||||
HAVE_EXT_ARGV=1
|
||||
continue
|
||||
fi
|
||||
opt=$1
|
||||
if [ "$opt" = "--" ]; then
|
||||
shift
|
||||
EXT_ARGV="$@"
|
||||
break
|
||||
fi
|
||||
shift
|
||||
if [ $(expr "$opt" : "-") -eq 0 ]; then
|
||||
# Option does not begin with a hyphen (-), so treat it as
|
||||
# a filename, directory, etc.
|
||||
if [ -z "$ARGV" ]; then
|
||||
ARGV="$opt"
|
||||
if [ "$HAVE_EXT_ARGV" ]; then
|
||||
# Previous line was -- so this and subsequent options are
|
||||
# really external argvs.
|
||||
if [ "$EXT_ARGV" ]; then
|
||||
EXT_ARGV="$EXT_ARGV $opt"
|
||||
else
|
||||
ARGV="$ARGV $opt"
|
||||
EXT_ARGV="$opt"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Save real opt from cmd line for error messages.
|
||||
local real_opt="$opt"
|
||||
|
||||
# Strip leading -- or --no- from option.
|
||||
if $(echo $opt | grep -q '^--no-'); then
|
||||
neg=1
|
||||
opt=$(echo $opt | sed 's/^--no-//')
|
||||
else
|
||||
neg=0
|
||||
opt=$(echo $opt | sed 's/^-*//')
|
||||
fi
|
||||
|
||||
# Find the option's spec file.
|
||||
if [ -f "$TMPDIR/po/$opt" ]; then
|
||||
spec="$TMPDIR/po/$opt"
|
||||
else
|
||||
spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
|
||||
if [ -z "$spec" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Unknown option: $real_opt" >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get the value specified for the option, if any. If the opt's spec
|
||||
# says it has a type, then it requires a value and that value should
|
||||
# be the next item ($1). Else, typeless options (like --version) are
|
||||
# either "yes" if specified, else "no" if negatable and --no-opt.
|
||||
local required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
|
||||
if [ -n "$required_arg" ]; then
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ "$next_opt_is_val" ]; then
|
||||
next_opt_is_val=""
|
||||
if [ $# -eq 0 ] || [ $(expr "$opt" : "-") -eq 1 ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "$real_opt requires a $required_arg argument" >&2
|
||||
continue
|
||||
else
|
||||
val="$1"
|
||||
shift
|
||||
fi
|
||||
val="$opt"
|
||||
opt_is_ok=1
|
||||
else
|
||||
if [ $neg -eq 0 ]; then
|
||||
val="yes"
|
||||
# If option does not begin with a hyphen (-), it's a filename, etc.
|
||||
if [ $(expr "$opt" : "-") -eq 0 ]; then
|
||||
if [ -z "$ARGV" ]; then
|
||||
ARGV="$opt"
|
||||
else
|
||||
ARGV="$ARGV $opt"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
# Save real opt from cmd line for error messages.
|
||||
real_opt="$opt"
|
||||
|
||||
# Strip leading -- or --no- from option.
|
||||
if $(echo $opt | grep '^--no-' >/dev/null); then
|
||||
opt_is_negated=1
|
||||
opt=$(echo $opt | sed 's/^--no-//')
|
||||
else
|
||||
val="no"
|
||||
opt_is_negated=""
|
||||
opt=$(echo $opt | sed 's/^-*//')
|
||||
fi
|
||||
|
||||
# Split opt=val pair.
|
||||
if $(echo $opt | grep '^[a-z-][a-z-]*=' >/dev/null 2>&1); then
|
||||
val="$(echo $opt | awk -F= '{print $2}')"
|
||||
opt="$(echo $opt | awk -F= '{print $1}')"
|
||||
fi
|
||||
|
||||
# Find the option's spec file.
|
||||
if [ -f "$TMPDIR/po/$opt" ]; then
|
||||
spec="$TMPDIR/po/$opt"
|
||||
else
|
||||
spec=$(grep "^short form:-$opt\$" "$TMPDIR"/po/* | cut -d ':' -f 1)
|
||||
if [ -z "$spec" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Unknown option: $real_opt" >&2
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get the value specified for the option, if any. If the opt's spec
|
||||
# says it has a type, then it requires a value and that value should
|
||||
# be the next item ($1). Else, typeless options (like --version) are
|
||||
# either "yes" if specified, else "no" if negatable and --no-opt.
|
||||
required_arg=$(cat "$spec" | awk -F: '/^type:/{print $2}')
|
||||
if [ "$required_arg" ]; then
|
||||
# Option takes a value.
|
||||
if [ "$val" ]; then
|
||||
opt_is_ok=1
|
||||
else
|
||||
next_opt_is_val=1
|
||||
fi
|
||||
else
|
||||
# Option does not take a value.
|
||||
if [ "$val" ]; then
|
||||
OPT_ERRS=$(($OPT_ERRS + 1))
|
||||
echo "Option $real_opt does not take a value" >&2
|
||||
continue
|
||||
fi
|
||||
if [ "$opt_is_negated" ]; then
|
||||
val=""
|
||||
else
|
||||
val="yes"
|
||||
fi
|
||||
opt_is_ok=1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Get and transform the opt's long form. E.g.: -q == --quiet == QUIET.
|
||||
opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
if [ "$opt_is_ok" ]; then
|
||||
# Get and transform the opt's long form. E.g.: -q == --quiet == QUIET.
|
||||
opt=$(cat "$spec" | grep '^long:' | cut -d':' -f2 | sed 's/-/_/g' | tr [:lower:] [:upper:])
|
||||
|
||||
# Re-eval the option to update its global variable value.
|
||||
eval "OPT_$opt"="$val"
|
||||
# Convert sizes.
|
||||
if grep "^type:size" "$spec" >/dev/null; then
|
||||
val=$(size_to_bytes $val)
|
||||
fi
|
||||
|
||||
# Re-eval the option to update its global variable value.
|
||||
eval "OPT_$opt"="'$val'"
|
||||
|
||||
opt=""
|
||||
val=""
|
||||
next_opt_is_val=""
|
||||
opt_is_ok=""
|
||||
opt_is_negated=""
|
||||
real_opt=""
|
||||
required_arg=""
|
||||
spec=""
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
size_to_bytes() {
|
||||
local size="$1"
|
||||
echo $size | perl -ne '%f=(B=>1, K=>1_024, M=>1_048_576, G=>1_073_741_824, T=>1_099_511_627_776); m/^(\d+)([kMGT])?/i; print $1 * $f{uc($2 || "B")};'
|
||||
}
|
||||
|
||||
# ###########################################################################
|
||||
# End parse_options package
|
||||
# ###########################################################################
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# This program is copyright 2011 Percona Inc.
|
||||
# This program is copyright 2011-2012 Percona Inc.
|
||||
# Feedback and improvements are welcome.
|
||||
#
|
||||
# THIS PROGRAM IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
|
||||
@@ -33,46 +33,53 @@ disk_space() {
|
||||
# Sub: check_disk_space
|
||||
# Check if there is or will be enough disk space. Input is a file
|
||||
# with output from <disk_space()>, i.e. `df -P -k`. The df output
|
||||
# must use 1k blocks, but the mb arg from the user is in MB.
|
||||
# must use 1k blocks, which should be POSIX standard.
|
||||
#
|
||||
# Arguments:
|
||||
# file - File with output from <disk_space()>.
|
||||
# mb - Minimum MB free.
|
||||
# pc - Minimum percent free.
|
||||
# mb_margin - Add this many MB to the real MB used.
|
||||
# file - File with output from <disk_space()>.
|
||||
# min_free_bytes - Minimum free bytes.
|
||||
# min_free_pct - Minimum free percentage.
|
||||
# bytes_margin - Add this many bytes to the real bytes used.
|
||||
#
|
||||
# Returns:
|
||||
# 0 if there is/will be enough disk space, else 1.
|
||||
check_disk_space() {
|
||||
local file="$1"
|
||||
local mb="${2:-0}"
|
||||
local pc="${3:-0}"
|
||||
local mb_margin="${4:-0}"
|
||||
local min_free_bytes="${2:-0}"
|
||||
local min_free_pct="${3:-0}"
|
||||
local bytes_margin="${4:-0}"
|
||||
|
||||
# Convert MB to KB because the df output should be in 1k blocks.
|
||||
local kb=$(($mb * 1024))
|
||||
local kb_margin=$(($mb_margin * 1024))
|
||||
# Real/actual bytes used and bytes free.
|
||||
local used_bytes=$(cat "$file" | awk '/^\//{print $3 * 1024}');
|
||||
local free_bytes=$(cat "$file" | awk '/^\//{print $4 * 1024}');
|
||||
local pct_used=$(cat "$file" | awk '/^\//{print $5}' | sed -e 's/%//g');
|
||||
local pct_free=$((100 - $pct_used))
|
||||
|
||||
local kb_used=$(cat "$file" | awk '/^\//{print $3}');
|
||||
local kb_free=$(cat "$file" | awk '/^\//{print $4}');
|
||||
local pc_used=$(cat "$file" | awk '/^\//{print $5}' | sed -e 's/%//g');
|
||||
# Report the real values to the user.
|
||||
local real_free_bytes=$free_bytes
|
||||
local real_pct_free=$pct_free
|
||||
|
||||
if [ "$kb_margin" -gt "0" ]; then
|
||||
local kb_total=$(($kb_used + $kb_free))
|
||||
# If there's a margin, we need to adjust the real values.
|
||||
if [ $bytes_margin -gt 0 ]; then
|
||||
used_bytes=$(($used_bytes + $bytes_margin))
|
||||
free_bytes=$(($free_bytes - $bytes_margin))
|
||||
pct_used=$(awk "BEGIN { printf(\"%d\", ($used_bytes/($used_bytes + $free_bytes)) * 100) }")
|
||||
|
||||
kb_used=$(($kb_used + $kb_margin))
|
||||
kb_free=$(($kb_free - $kb_margin))
|
||||
pc_used=$(awk "BEGIN { printf(\"%d\", $kb_used/$kb_total * 100) }")
|
||||
pct_free=$((100 - $pct_used))
|
||||
fi
|
||||
|
||||
local pc_free=$((100 - $pc_used))
|
||||
if [ $free_bytes -lt $min_free_bytes -o $pct_free -lt $min_free_pct ]; then
|
||||
warn "Not enough free disk space:
|
||||
Limit: ${min_free_pct}% free, ${min_free_bytes} bytes free
|
||||
Actual: ${real_pct_free}% free, ${real_free_bytes} bytes free (- $bytes_margin bytes margin)
|
||||
"
|
||||
# Print the df that we used.
|
||||
cat "$file" >&2
|
||||
|
||||
if [ "$kb_free" -le "$kb" -o "$pc_free" -le "$pc" ]; then
|
||||
warn "Not enough free disk space: ${pc_free}% free, ${kb_free} KB free; wanted more than ${pc}% free or ${kb} KB free"
|
||||
return 1
|
||||
return 1 # not enough disk space
|
||||
fi
|
||||
|
||||
return 0
|
||||
return 0 # disk space is OK
|
||||
}
|
||||
|
||||
# ###########################################################################
|
||||
|
@@ -5,6 +5,7 @@ TESTS=19
|
||||
TMPFILE="$TEST_TMPDIR/parse-opts-output"
|
||||
TMPDIR="$TEST_TMPDIR"
|
||||
PATH="$PATH:$PERCONA_TOOLKIT_SANDBOX/bin"
|
||||
TOOL="pt-stalk"
|
||||
|
||||
mkdir "$TMPDIR/collect" 2>/dev/null
|
||||
|
||||
@@ -14,7 +15,7 @@ source "$LIB_DIR/safeguards.sh"
|
||||
source "$LIB_DIR/alt_cmds.sh"
|
||||
source "$LIB_DIR/collect.sh"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po002.sh" --run-time 1 -- --defaults-file=/tmp/12345/my.sandbox.cnf
|
||||
parse_options "$BIN_DIR/pt-stalk" --run-time 1 -- --defaults-file=/tmp/12345/my.sandbox.cnf
|
||||
|
||||
# Prefix (with path) for the collect files.
|
||||
local p="$TMPDIR/collect/2011_12_05"
|
||||
@@ -111,7 +112,7 @@ is "$iters" "1" "1 iteration/1s run time"
|
||||
# Try longer run time.
|
||||
# ###########################################################################
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po002.sh" --run-time 2 -- --defaults-file=/tmp/12345/my.sandbox.cnf
|
||||
parse_options "$BIN_DIR/pt-stalk" --run-time 2 -- --defaults-file=/tmp/12345/my.sandbox.cnf
|
||||
|
||||
rm $TMPDIR/collect/*
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TESTS=37
|
||||
TESTS=73
|
||||
|
||||
TMPFILE="$TEST_TMPDIR/parse-opts-output"
|
||||
TOOL="pt-stalk"
|
||||
@@ -13,7 +13,7 @@ source "$LIB_DIR/parse_options.sh"
|
||||
# Parse options from POD using all default values.
|
||||
# ############################################################################
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" "" 2>$TMPFILE
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" 2>$TMPFILE
|
||||
|
||||
is "`cat $TMPFILE`" "" "No warnings or errors"
|
||||
|
||||
@@ -38,6 +38,8 @@ is "$OPT_NOPTION" "yes" "Default neg option (spec)"
|
||||
is "$OPT_INT_OPT" "50" "Specified int option (spec)"
|
||||
is "$OPT_INT_OPT2" "42" "Default int option with default (spec)"
|
||||
is "$OPT_VERSION" "" "--version (spec)"
|
||||
is "$ARGV" "" "ARGV"
|
||||
is "$EXT_ARGV" "" "External ARGV"
|
||||
|
||||
# ############################################################################
|
||||
# --option=value should work like --option value.
|
||||
@@ -47,6 +49,10 @@ parse_options "$T_LIB_DIR/samples/bash/po001.sh" --int-opt=42
|
||||
|
||||
is "$OPT_INT_OPT" "42" "Specified int option (--option=value)"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --string-opt="hello world"
|
||||
|
||||
is "$OPT_STRING_OPT" "hello world" "Specified int option (--option=\"value\")"
|
||||
|
||||
# ############################################################################
|
||||
# Negate an option like --no-option.
|
||||
# ############################################################################
|
||||
@@ -56,7 +62,7 @@ parse_options "$T_LIB_DIR/samples/bash/po001.sh" --no-noption
|
||||
is "$OPT_STRING_OPT" "" "Default string option (neg)"
|
||||
is "$OPT_STRING_OPT2" "foo" "Default string option with default (neg)"
|
||||
is "$OPT_TYPELESS_OPTION" "" "Default typeless option (neg)"
|
||||
is "$OPT_NOPTION" "no" "Negated option (neg)"
|
||||
is "$OPT_NOPTION" "" "Negated option (neg)"
|
||||
is "$OPT_INT_OPT" "" "Default int option (neg)"
|
||||
is "$OPT_INT_OPT2" "42" "Default int option with default (neg)"
|
||||
is "$OPT_VERSION" "" "--version (neg)"
|
||||
@@ -68,6 +74,16 @@ is "$OPT_VERSION" "" "--version (neg)"
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" -v
|
||||
is "$OPT_VERSION" "yes" "Short form"
|
||||
|
||||
# ############################################################################
|
||||
# Command line options plus externals args.
|
||||
# ############################################################################
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --no-noption -- --foo
|
||||
|
||||
is "$OPT_NOPTION" "" "Negated option (--)"
|
||||
is "$ARGV" "" "ARGV (--)"
|
||||
is "$EXT_ARGV" "--foo" "External ARGV (--)"
|
||||
|
||||
# ############################################################################
|
||||
# An unknown option should produce an error.
|
||||
# ############################################################################
|
||||
@@ -89,6 +105,14 @@ cmd_ok \
|
||||
"grep -q \"For more information, 'man pt-stalk' or 'perldoc\" $TMPFILE" \
|
||||
"--help"
|
||||
|
||||
cmd_ok \
|
||||
"grep -q ' --string-opt2[ ]*String option with a default.' $TMPFILE" \
|
||||
"Command line options"
|
||||
|
||||
cmd_ok \
|
||||
"grep -q '\-\-string-opt[ ]*(No value)' $TMPFILE" \
|
||||
"Options and values after processing arguments"
|
||||
|
||||
# Don't interpolate.
|
||||
parse_options "$T_LIB_DIR/samples/bash/po003.sh" --help
|
||||
usage_or_errors "$T_LIB_DIR/samples/bash/po003.sh" >$TMPFILE 2>&1
|
||||
@@ -103,7 +127,7 @@ cmd_ok \
|
||||
TOOL="pt-test"
|
||||
cp "$T_LIB_DIR/samples/bash/config001.conf" "$HOME/.$TOOL.conf"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" ""
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh"
|
||||
|
||||
is "$OPT_STRING_OPT" "abc" "Default string option (conf)"
|
||||
is "$OPT_STRING_OPT2" "foo" "Default string option with default (conf)"
|
||||
@@ -112,16 +136,85 @@ is "$OPT_NOPTION" "yes" "Default neg option (conf)"
|
||||
is "$OPT_INT_OPT" "" "Default int option (conf)"
|
||||
is "$OPT_INT_OPT2" "42" "Default int option with default (conf)"
|
||||
is "$OPT_VERSION" "" "--version (conf)"
|
||||
is "$EXT_ARGV" "--host 127.1 --user daniel" "External ARGV (conf)"
|
||||
is "$ARGV" "" "ARGV (conf)"
|
||||
is "$EXT_ARGV" "--host=127.1 --user=daniel" "External ARGV (conf)"
|
||||
|
||||
# Command line should override config file.
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --string-opt zzz
|
||||
|
||||
is "$OPT_STRING_OPT" "zzz" "Command line overrides config file"
|
||||
|
||||
# User-specified --config
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --config "$T_LIB_DIR/samples/bash/config003.conf" --string-opt bar
|
||||
|
||||
is "$OPT_STRING_OPT" "bar" "--config string option"
|
||||
is "$OPT_STRING_OPT2" "foo" "--config string option2"
|
||||
is "$OPT_TYPELESS_OPTION" "" "--config typeless option"
|
||||
is "$OPT_NOPTION" "yes" "--config negatable option"
|
||||
is "$OPT_INT_OPT" "123" "--config int option"
|
||||
is "$OPT_INT_OPT2" "42" "--config int option2"
|
||||
is "$OPT_VERSION" "" "--config version option"
|
||||
is "$ARGV" "" "--config ARGV"
|
||||
is "$EXT_ARGV" "" "--config External ARGV"
|
||||
|
||||
# Multiple --config files, last should take precedence.
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --config $T_LIB_DIR/samples/bash/config001.conf,$T_LIB_DIR/samples/bash/config002.conf
|
||||
|
||||
is "$OPT_STRING_OPT" "hello world" "Two --config string option"
|
||||
is "$OPT_TYPELESS_OPTION" "yes" "Two --config typeless option"
|
||||
is "$OPT_INT_OPT" "100" "Two --config int option"
|
||||
is "$ARGV" "" "Two --config ARGV"
|
||||
is "$EXT_ARGV" "--host=127.1 --user=daniel" "Two--config External ARGV"
|
||||
|
||||
# ############################################################################
|
||||
# Option values with spaces.
|
||||
# ############################################################################
|
||||
|
||||
# Config file
|
||||
cp "$T_LIB_DIR/samples/bash/config002.conf" "$HOME/.$TOOL.conf"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" ""
|
||||
|
||||
is "$OPT_STRING_OPT" "hello world" "Option value with space (conf)"
|
||||
is "$OPT_INT_OPT" "100" "Option = value # comment (conf)"
|
||||
|
||||
rm "$HOME/.$TOOL.conf"
|
||||
TOOL="pt-stalk"
|
||||
|
||||
# Command line
|
||||
parse_options "$T_LIB_DIR/samples/bash/po001.sh" --string-opt "hello world"
|
||||
is "$OPT_STRING_OPT" "hello world" "Option value with space (cmd line)"
|
||||
is "$ARGV" "" "ARGV (cmd line)"
|
||||
is "$EXT_ARGV" "" "External ARGV (cmd line)"
|
||||
|
||||
# ############################################################################
|
||||
# Size options.
|
||||
# ############################################################################
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1T
|
||||
is "$OPT_DISK_BYTES_FREE" "1099511627776" "Size: 1T"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1G
|
||||
is "$OPT_DISK_BYTES_FREE" "1073741824" "Size: 1G"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1M
|
||||
is "$OPT_DISK_BYTES_FREE" "1048576" "Size: 1M"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1K
|
||||
is "$OPT_DISK_BYTES_FREE" "1024" "Size: 1K"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1k
|
||||
is "$OPT_DISK_BYTES_FREE" "1024" "Size: 1k"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 1
|
||||
is "$OPT_DISK_BYTES_FREE" "1" "Size: 1"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh" --disk-bytes-free 100M
|
||||
is "$OPT_DISK_BYTES_FREE" "104857600" "Size: 100M"
|
||||
|
||||
parse_options "$T_LIB_DIR/samples/bash/po004.sh"
|
||||
is "$OPT_DISK_BYTES_FREE" "104857600" "Size: 100M default"
|
||||
|
||||
# ############################################################################
|
||||
# Done
|
||||
# ############################################################################
|
||||
|
@@ -18,36 +18,51 @@ is \
|
||||
"2" \
|
||||
"2-line df output"
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 22000 18 >$TMPDIR/out 2>&1
|
||||
# Filesystem 1024-blocks Used Available Capacity Mounted on
|
||||
# /dev/disk0s2 118153176 94409664 23487512 81% /
|
||||
#
|
||||
# Those values are in Kb, so:
|
||||
# used = 94409664 (94.4G) = 96_675_495_936 bytes
|
||||
# free = 23487512 (23.4G) = 24_051_212_288 bytes
|
||||
# pct free = 100 - 81 = 19 %
|
||||
|
||||
# want free - 100, 18 < 19, so this should be ok.
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212188 18 >$TMPDIR/out 2>&1
|
||||
is "$?" "0" "Enough disk space"
|
||||
is \
|
||||
"`cat $TMPDIR/out`" \
|
||||
"" \
|
||||
"No output if enough disk space"
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24000 18 >$TMPDIR/out 2>&1
|
||||
# want free - 100 is ok, but 20 < 19 is not.
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212188 20 >$TMPDIR/out 2>&1
|
||||
is "$?" "1" "Not enough % free"
|
||||
|
||||
# want free + 100, so this should fail
|
||||
# (real free is 100 bytes under what we want)
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212388 18 >$TMPDIR/out 2>&1
|
||||
is "$?" "1" "Not enough MB free"
|
||||
cmd_ok \
|
||||
"grep -q '19% free, 23487512 KB free; wanted more than 18% free or 24576000 KB free' $TMPDIR/out" \
|
||||
"grep -q 'Actual: 19% free, 24051212288 bytes free (- 0 bytes margin)' $TMPDIR/out" \
|
||||
"Warning if not enough disk space"
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 22000 19 >$TMPDIR/out 2>&1
|
||||
is "$?" "1" "Not enough % free"
|
||||
|
||||
# ###########################################################################
|
||||
# Check with a margin (amount we plan to use in the future).
|
||||
# ###########################################################################
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 22000 18 100
|
||||
# want free - 100 + 50 margin, so effectively want free - 50 is ok.
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212188 18 50
|
||||
is "$?" "0" "Enough disk space with margin"
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 23000 18 100 >$TMPDIR/out 2>&1
|
||||
# want free - 100 + 101 margin, so real free is 1 byte under what we want.
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212188 18 101 >$TMPDIR/out 2>&1
|
||||
is "$?" "1" "Not enough MB free with margin"
|
||||
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 100 5 20000 >$TMPDIR/out 2>&1
|
||||
# want free - 100 + 50 margin ok but %free will be 19 which is < 25.
|
||||
check_disk_space "$SAMPLE/diskspace001.txt" 24051212188 25 50 >$TMPDIR/out 2>&1
|
||||
is "$?" "1" "Not enough % free with margin"
|
||||
cmd_ok \
|
||||
"grep -q '3% free,' $TMPDIR/out" \
|
||||
"grep -q 'Actual:[ ]*19% free,' $TMPDIR/out" \
|
||||
"Calculates % free with margin"
|
||||
|
||||
# ###########################################################################
|
||||
|
@@ -1,5 +1,5 @@
|
||||
--string-opt=abc
|
||||
--typeless-option
|
||||
string-opt=abc
|
||||
typeless-option
|
||||
--
|
||||
--host=127.1
|
||||
--user=daniel
|
||||
|
5
t/lib/samples/bash/config002.conf
Normal file
5
t/lib/samples/bash/config002.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
# Line comment.
|
||||
string-opt=hello world
|
||||
|
||||
|
||||
int-opt = 100 # Inline comment.
|
2
t/lib/samples/bash/config003.conf
Normal file
2
t/lib/samples/bash/config003.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
string-opt=from config file
|
||||
int-opt=123
|
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
:
|
||||
|
||||
# ############################################################################
|
||||
# Documentation
|
||||
# ############################################################################
|
||||
:<<'DOCUMENTATION'
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
pt-stalk - Wait for a condition to occur then begin collecting data.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item --collect
|
||||
|
||||
default: yes; negatable: yes
|
||||
|
||||
Collect system information.
|
||||
|
||||
=item --collect-gdb
|
||||
|
||||
Collect GDB stacktraces.
|
||||
|
||||
=item --collect-oprofile
|
||||
|
||||
Collect oprofile data.
|
||||
|
||||
=item --collect-strace
|
||||
|
||||
Collect strace data.
|
||||
|
||||
=item --collect-tcpdump
|
||||
|
||||
Collect tcpdump data.
|
||||
|
||||
=item --cycles
|
||||
|
||||
type: int; default: 5
|
||||
|
||||
Number of times condition must be met before triggering collection.
|
||||
|
||||
=item --daemonize
|
||||
|
||||
default: yes; negatable: yes
|
||||
|
||||
Daemonize the tool.
|
||||
|
||||
=item --dest
|
||||
|
||||
type: string
|
||||
|
||||
Where to store collected data.
|
||||
|
||||
=item --disk-byte-limit
|
||||
|
||||
type: int; default: 100
|
||||
|
||||
Exit if the disk has less than this many MB free.
|
||||
|
||||
=item --disk-pct-limit
|
||||
|
||||
type: int; default: 5
|
||||
|
||||
Exit if the disk is less than this %full.
|
||||
|
||||
=item --execute-command
|
||||
|
||||
type: string; default: pt-collect
|
||||
|
||||
Location of the C<pt-collect> tool.
|
||||
|
||||
=item --function
|
||||
|
||||
type: string; default: status
|
||||
|
||||
Built-in function name or plugin file name which returns the value of C<VARIABLE>.
|
||||
|
||||
Possible values are:
|
||||
|
||||
=over
|
||||
|
||||
=item * status
|
||||
|
||||
Grep the value of C<VARIABLE> from C<mysqladmin extended-status>.
|
||||
|
||||
=item * processlist
|
||||
|
||||
Count the number of processes in C<mysqladmin processlist> whose
|
||||
C<VARIABLE> column matches C<MATCH>. For example:
|
||||
|
||||
TRIGGER_FUNCTION="processlist" \
|
||||
VARIABLE="State" \
|
||||
MATCH="statistics" \
|
||||
THRESHOLD="10"
|
||||
|
||||
The above triggers when more than 10 processes are in the "statistics" state.
|
||||
C<MATCH> must be specified for this trigger function.
|
||||
|
||||
=item * magic
|
||||
|
||||
TODO
|
||||
|
||||
=item * plugin file name
|
||||
|
||||
A plugin file allows you to specify a custom trigger function. The plugin
|
||||
file must contain a function called C<trg_plugin>. For example:
|
||||
|
||||
trg_plugin() {
|
||||
# Do some stuff.
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
The last output if the function (its "return value") must be a number.
|
||||
This number is compared to C<THRESHOLD>. All L<"ENVIRONMENT"> variables
|
||||
are available to the function.
|
||||
|
||||
Do not alter the tool's existing global variables. Prefix any plugin-specific
|
||||
global variables with "PLUGIN_".
|
||||
|
||||
=back
|
||||
|
||||
=item --help
|
||||
|
||||
Print help and exit.
|
||||
|
||||
=item --interval
|
||||
|
||||
type: int; default: 1
|
||||
|
||||
Interval between checks.
|
||||
|
||||
=item --iterations
|
||||
|
||||
type: int
|
||||
|
||||
Exit after triggering C<pt-collect> this many times. By default, the tool
|
||||
will collect as many times as it's triggered.
|
||||
|
||||
=item --log
|
||||
|
||||
type: string; default: /var/log/pt-stalk.log
|
||||
|
||||
Print all output to this file when daemonized.
|
||||
|
||||
=item --match
|
||||
|
||||
type: string
|
||||
|
||||
Match pattern for C<processles> L<"--function">.
|
||||
|
||||
=item --notify-by-email
|
||||
|
||||
type: string
|
||||
|
||||
Send mail to this list of addresses when C<pt-collect> triggers.
|
||||
|
||||
=item --pid FILE
|
||||
|
||||
type: string; default: /var/run/pt-stalk.pid
|
||||
|
||||
Create a PID file when daemonized.
|
||||
|
||||
=item --retention-time
|
||||
|
||||
type: int; default: 30
|
||||
|
||||
Remove samples after this many days.
|
||||
|
||||
=item --run-time
|
||||
|
||||
type: int; default: 30
|
||||
|
||||
How long to collect statistics data for?
|
||||
|
||||
Make sure that this isn't longer than SLEEP.
|
||||
|
||||
=item --sleep
|
||||
|
||||
type: int; default: 300
|
||||
|
||||
How long to sleep after collecting?
|
||||
|
||||
=item --threshold N
|
||||
|
||||
type: int; default: 25
|
||||
|
||||
Max number of C<N> to tolerate.
|
||||
|
||||
=item --variable NAME
|
||||
|
||||
type: string; default: Threads_running
|
||||
|
||||
This is the thing to check for.
|
||||
|
||||
=item --version
|
||||
|
||||
Print tool's version and exit.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ENVIRONMENT
|
||||
|
||||
No env vars used.
|
||||
|
||||
=cut
|
||||
|
||||
DOCUMENTATION
|
37
t/lib/samples/bash/po004.sh
Normal file
37
t/lib/samples/bash/po004.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
:
|
||||
|
||||
# ############################################################################
|
||||
# Documentation
|
||||
# ############################################################################
|
||||
:<<'DOCUMENTATION'
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
pt-stalk - Wait for a condition to occur then begin collecting data.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item --disk-bytes-free
|
||||
|
||||
type: size; default: 100M
|
||||
|
||||
Fall apart if there's less than this many bytes free on the disk.
|
||||
|
||||
=item --help
|
||||
|
||||
Print help.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ENVIRONMENT
|
||||
|
||||
No env vars used.
|
||||
|
||||
=cut
|
||||
|
||||
DOCUMENTATION
|
@@ -10,6 +10,7 @@ use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use English qw(-no_match_vars);
|
||||
use Test::More;
|
||||
use Time::HiRes qw(sleep);
|
||||
|
||||
use PerconaTest;
|
||||
use DSNParser;
|
||||
@@ -23,7 +24,7 @@ if ( !$dbh ) {
|
||||
plan skip_all => 'Cannot connect to sandbox master';
|
||||
}
|
||||
else {
|
||||
plan tests => 15;
|
||||
plan tests => 20;
|
||||
}
|
||||
|
||||
my $cnf = "/tmp/12345/my.sandbox.cnf";
|
||||
@@ -168,6 +169,67 @@ like(
|
||||
"Trigger file logs how pt-stalk was ran"
|
||||
);
|
||||
|
||||
# ###########################################################################
|
||||
# Triggered but --no-collect.
|
||||
# ###########################################################################
|
||||
diag(`rm $pid_file 2>/dev/null`);
|
||||
diag(`rm $log_file 2>/dev/null`);
|
||||
diag(`rm $dest/* 2>/dev/null`);
|
||||
|
||||
(undef, $uptime) = $dbh->selectrow_array("SHOW STATUS LIKE 'Uptime'");
|
||||
$threshold = $uptime + 2;
|
||||
|
||||
$retval = system("$trunk/bin/pt-stalk --no-collect --iterations 1 --dest $dest --variable Uptime --threshold $threshold --cycles 1 --run-time 1 --pid $pid_file -- --defaults-file=$cnf >$log_file 2>&1");
|
||||
|
||||
sleep 2;
|
||||
|
||||
$output = `cat $log_file`;
|
||||
like(
|
||||
$output,
|
||||
qr/Collect triggered/,
|
||||
"Collect triggered"
|
||||
);
|
||||
|
||||
ok(
|
||||
! -f "$dest/*",
|
||||
"No files collected"
|
||||
);
|
||||
|
||||
$output = `ps x | grep -v grep | grep 'pt-stalk pt-stalk --iterations 1 --dest $dest'`;
|
||||
is(
|
||||
$output,
|
||||
"",
|
||||
"pt-stalk is not running"
|
||||
);
|
||||
|
||||
# #############################################################################
|
||||
# --config
|
||||
# #############################################################################
|
||||
|
||||
diag(`cp $ENV{HOME}/.pt-stalk.conf $ENV{HOME}/.pt-stalk.conf.original 2>/dev/null`);
|
||||
diag(`cp $trunk/t/pt-stalk/samples/config001.conf $ENV{HOME}/.pt-stalk.conf`);
|
||||
|
||||
system "$trunk/bin/pt-stalk --dest $dest --pid $pid_file >$log_file 2>&1 &";
|
||||
PerconaTest::wait_for_files($pid_file);
|
||||
sleep 1;
|
||||
chomp($pid = `cat $pid_file`);
|
||||
$retval = system("kill $pid 2>/dev/null");
|
||||
is(
|
||||
$retval >> 0,
|
||||
0,
|
||||
"Killed pt-stalk"
|
||||
);
|
||||
|
||||
$output = `cat $log_file`;
|
||||
like(
|
||||
$output,
|
||||
qr/Check results: Aborted_connects=|variable=Aborted_connects/,
|
||||
"Read default config file"
|
||||
);
|
||||
|
||||
diag(`rm $ENV{HOME}/.pt-stalk.conf`);
|
||||
diag(`cp $ENV{HOME}/.pt-stalk.conf.original $ENV{HOME}/.pt-stalk.conf 2>/dev/null`);
|
||||
|
||||
# #############################################################################
|
||||
# Done.
|
||||
# #############################################################################
|
||||
|
8
t/pt-stalk/samples/config001.conf
Normal file
8
t/pt-stalk/samples/config001.conf
Normal file
@@ -0,0 +1,8 @@
|
||||
--iterations=1
|
||||
--variable=Aborted_connects
|
||||
--threshold=999999
|
||||
--
|
||||
-umsandbox
|
||||
-pmsandbox
|
||||
--host 127.1
|
||||
--port 12345
|
Reference in New Issue
Block a user