blob: 0aef00344406c2f588449aafa12e4b0414b5541a [file] [log] [blame] [raw]
#!/bin/sh
# vzubc - a tool for displaying OpenVZ user beancounters.
# Copyright (C) 2011-2013, Parallels, Inc. All rights reserved.
# Licensed under GNU GPL v2 or later version.
umask 0077
# Ratio of held/limit to bypass quiet
Q_HELD=0.5
# Ratio of maxheld/limit to bypass quiet
Q_MAXHELD=0.8
# Directory to store previous failcnt values for relative mode
STOREDIR=/tmp/vzubc.store
# Colors
C_BRIGHT="\\033[1;32m"
C_ERROR="\\033[1;31m"
C_WARNING="\\033[1;33m"
C_NORMAL="\\033[0;39m"
usage() {
cat << EOF
Usage: vzubc [option ...] [<ctid> ...]
-w, --watch:
Run itself under watch(1) (a la top mode, Ctrl-C to exit).
-wd: show differences between runs
-wt: do not show watch title
-wn <time>: refresh every <time> seconds
-q, --quiet:
Quiet mode (only show beancounters with fails and close to limits).
-qh <ratio>: quiet threshold for held/limit ratio (default: $Q_HELD)
-qm <ratio>: quiet threshold for maxheld/limit ratio (default: $Q_MAXHELD)
-v, --verbose:
Verbose mode (show unlimited beancounters as well).
-r, --relative:
Relative mode, show fail counters delta compared to previous run.
-rc: clear all saved fail counter data for relative mode
-rd <dir>: directory for storing data (default: $STOREDIR)
-i, --incremental:
Incremental mode, add held value delta compared to previous run.
If used together with -q, beancounters with changed values are also shown.
-ic: clear all saved held data for incremental mode
-id <dir>: synonym for -rd
-c, --color:
Enable color highlighting (using same thresholds as for --quiet).
Color mode is not compatible with --watch.
-f, --file <file>:
File to read UBC values from. Defaults are /proc/bc/resources or
/proc/user_beancounters. Use - to read from stdin.
<ctid>: container name/ID to show info about (can be used multiple times)
EOF
exit $1
}
CTIDS=""
FILE=""
WATCH=""
ARGV=$*
# For vzlist to work, make sure @SBINDIR@ is in $PATH
if ! echo ":${PATH}:" | fgrep -q ':@SBINDIR@:'; then
PATH="@SBINDIR@:$PATH"
fi
chk_zeroarg() {
if test -z "$2"; then
echo "Error: option $1 requires an argument" 1>&2
usage 1
fi
}
while test $# -gt 0; do
case $1 in
-W) # Internal use only
watch=
;;
-w|--watch)
watch=yes
;;
-wd)
watch=yes
WATCH_ARGS="$WATCH_ARGS -d"
;;
-wt)
watch=yes
WATCH_ARGS="$WATCH_ARGS -t"
;;
-wn)
chk_zeroarg $1 $2
watch=yes
WATCH_ARGS="$WATCH_ARGS -n $2"
shift
;;
-q|--quiet)
quiet=1
;;
-qh)
chk_zeroarg $1 $2
thr=1
Q_HELD=$2
shift
;;
-qm)
chk_zeroarg $1 $2
thr=1
Q_MAXHELD=$2
shift
;;
-v|--verbose)
verbose=1
;;
-r|--relative)
relative=1
;;
-rc)
rm -f $STOREDIR/ubc.*.failcnt
;;
-rd|-id)
chk_zeroarg $1 $2
STOREDIR=$2
shift
;;
-i|--incremental)
incremental=1
;;
-ic)
rm -f $STOREDIR/ubc.*.held
;;
-c|--color)
color=1
AWK_COLORS="-v c_h=$C_BRIGHT -v c_n=$C_NORMAL -v c_w=$C_WARNING -v c_e=$C_ERROR"
;;
-h|--help)
usage 0
;;
-f|--file)
chk_zeroarg $1 $2
FILE=$2
shift
;;
-*)
echo "Unrecognized option: $1" 1>&2
usage 1
;;
*)
# Try to convert CT name to ID
ID=$(vzlist -H -o ctid "$1" 2>/dev/null)
if test $? -eq 0; then
CTIDS=$(echo $CTIDS $ID)
else
CTIDS=$(echo $CTIDS $1)
fi
;;
esac
shift
done
# Sanity checks
if test -n "$watch" -a -n "$color"; then
echo "Error: can't use --watch and --color together" 1>&2
usage 1
fi
if test -n "$thr" -a -z "$color" -a -z "$quiet"; then
echo "Error: -qh and -qm only make sense with --quiet or --color" 1>&2
usage 1
fi
if test -n "$quiet" -a -n "$verbose"; then
echo "Error: options --quiet and --verbose can't be used together" 1>&2
usage 1
fi
if test -z "$FILE"; then
# No file name given, substitute sane default
FILE=/proc/bc/resources
test -f $FILE || FILE=/proc/user_beancounters
fi
# Test that input file is readable
if test "$FILE" != "-"; then
cat < "$FILE" > /dev/null || exit 1
fi
# Relative/incremental mode preparations and sanity checks
if test -n "$relative" -o -n "$incremental"; then
# Create dir if needed
if ! test -d $STOREDIR; then
mkdir $STOREDIR || exit 1
fi
# Check we can write to it
touch $STOREDIR/ubc.test || exit 1
rm -f $STOREDIR/ubc.test || exit 1
fi
# Re-exec ourselves under watch
test -z "$watch" || exec watch $WATCH_ARGS -- $0 $ARGV -W
cat $FILE | LANG=C awk -v CTIDS=" ${CTIDS} " \
-v quiet="$quiet" -v verbose="$verbose" \
-v qheld="$Q_HELD" -v qmaxheld="$Q_MAXHELD" \
-v rel="$relative" -v storedir="$STOREDIR" \
-v inc="$incremental" $AWK_COLORS \
'
function hr(res, v) {
if ((v == 9223372036854775807) || (v == 2147483647) || (v == 0))
return "- ";
i=1
if ((res ~ /pages$/) && (v != 0)) {
v = v*4; i++
}
while (v >= 1024) {
v=v/1024
i++
}
fmt="%d%c"
if (v < 100)
fmt="%.3g%c"
return sprintf(fmt, v, substr(" KMGTPEZY", i, 1))
}
function dp(p, d) {
if ((d == 0) || (d == 9223372036854775807) || (d == 2147483647))
return "- "
r = sprintf("%.1f", p / d * 100);
fmt="%d"
if (r < 10)
fmt="%.1g"
r = sprintf(fmt, r)
if (r == 0)
return "- "
return r "%"
}
function unlim(barrier, limit) {
if ((barrier == 9223372036854775807) || (barrier == 2147483647))
return 1;
return 0;
}
function important(id, held, maxheld, barrier, limit, failcnt) {
if (failcnt > 0)
return 2;
if (barrier == 0)
barrier = limit;
if (held > barrier)
return 2;
if (held > barrier * qheld)
return 1;
if (maxheld > barrier * qmaxheld)
return 1;
return 0;
}
function prepare_header() {
header=sprintf( c_h \
"----------------------------------------------------------------%s\n" \
"CT %-10s| HELD%s Bar%% Lim%%| MAXH Bar%% Lim%%| BAR | LIM |%cFAIL\n" \
"-------------+-%s--------------+---------------+-----+-----+------" \
c_n, inc ? "-------" : "",
bcid, inc ? " +/- " : "", rel ? "+" : " ", inc ? "-------" : "")
}
BEGIN {
if (qheld > 1)
qheld /= 100
if (qmaxheld > 1)
qmaxheld /= 100
if (qheld > qmaxheld)
qheld=qmaxheld
prepare_header()
bcid=-1
}
/^Version: / {
if ($2 != "2.5") {
print "Error: unknown version:",
$2 > "/dev/stderr"
exit 1
}
next
}
/^[[:space:]]*uid / {
next
}
/^[[:space:]]*dummy/ {
id=""
next
}
/^[[:space:]]*[0-9]+:/ {
header=""
bcid=int($1)
if ((CTIDS !~ /^[ ]*$/) && (CTIDS !~ " " bcid " ")) {
skip=1
next
}
skip=0
prepare_header()
id=$2
held=$3
maxheld=$4
barrier=$5
limit=$6
failcnt=$7
}
/^[[:space:]]*[a-z]+/ {
id=$1
held=$2
maxheld=$3
barrier=$4
limit=$5
failcnt=$6
}
((id!="") && (!skip)) {
if ((bcid < 0) && (rel || inc)) {
print "Error: can not use relative/incremental" \
" modes: BCID is unknown" > "/dev/stderr"
exit 1
}
newfc=failcnt
store=storedir "/ubc." bcid "." id
if ( (rel) && (failcnt > 0) ) {
f_file=store ".failcnt"
getline oldfc < f_file
if (oldfc > 0)
failcnt=failcnt-oldfc
if (failcnt < 0)
failcnt=newfc
}
save_held=0
dh=0
if (inc) {
d_held=" "
h_file=store ".held"
save_held=1
getline o_held < h_file
if (o_held >= 0) {
dh=held - o_held
sig="+"
if (dh < 0) {
dh=-dh; sig="-"
}
if (dh != 0)
d_held = sprintf("%7s", sig hr(id, dh))
else
save_held=0
}
}
imp=important(id, held, maxheld, barrier, limit, failcnt)
unl=unlim(barrier, limit)
if ((((quiet) && (!imp)) || ((!verbose) && (unl))) && (dh==0)) {
id=""
next
}
if (header != "") {
if (printed)
print ""
print header
printed=1
header=""
}
if (imp == 2)
printf c_e;
else if (imp == 1)
printf c_w;
else
printf c_n;
printf "%13s|%5s%s %4s %4s|%5s %4s %4s|%5s|%5s| %5s\n" c_n,
id,
hr(id, held), d_held, dp(held, barrier), dp(held, limit),
hr(id, maxheld), dp(maxheld, barrier), dp(maxheld, limit),
hr(id, barrier), hr(id, limit), hr("", failcnt)
if ( (rel) && (newfc > 0) ) {
print newfc > f_file
close(f_file)
}
if (save_held) {
print held > h_file
close(h_file)
}
id=""
}
END {
if (printed)
printf "----------------------------------------------------------------%s\n", inc ? "-------" : ""
}
'