blob: fd3de555cd532509a7e85e1693427a7f49b3ecea [file] [log] [blame] [raw]
#!/bin/sh
# vzubc - a tool for displaying OpenVZ user beancounters.
# Copyright (C) 2011, 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.8
# Ratio of maxheld/limit to bypass quiet
Q_MAXHELD=0.5
# Directory to store previous failcnt values for cumulative mode
STOREDIR=/tmp/vzubc.store
usage() {
cat << EOF
Usage: vzubc [option ...] [<filename>] [<ctid> ...]
-w: run under watch(1) (a la top mode, Ctrl-C to exit)
-wd: show differences between runs
-wn <time>: refresh every <time> seconds
-q: quiet mode (only show beancounters with fails and close to limits)
-qh <ratio>: quiet watermark for held/limit ratio (default: $Q_HELD)
-qm <ratio>: quiet watermark for maxheld/limit ratio (default: $Q_MAXHELD)
-c: cumulative mode (show fail counters relative to previous run)
-cc: clear all saved fail counter data for cumulative mode
-cd <dir>: directory for cumulative data (default: $STOREDIR)
<filename>: file to read UBC values from (defaults are
/proc/bc/resources or /proc/user_beancounters)
<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)
if ! which watch >/dev/null; then
echo "Error: watch binary not found" 1>&2
exit 1
fi
watch=yes
;;
-wd)
watch=yes
WATCH_ARGS="$WATCH_ARGS -d"
;;
-wn)
chk_zeroarg $1 $2
watch=yes
WATCH_ARGS="$WATCH_ARGS -n $2"
shift
;;
-q)
quiet=1
;;
-qh)
chk_zeroarg $1 $2
quiet=1
Q_HELD=$2
shift
;;
-qm)
chk_zeroarg $1 $2
quiet=1
Q_MAXHELD=$2
shift
;;
-c)
cumulative=1
;;
-cc)
cumulative=1
rm -fr $STOREDIR/
;;
-cd)
chk_zeroarg $1 $2
cumulative=1
STOREDIR=$2
;;
-h|--help)
usage 0
;;
-)
# Use stdin for input
FILE="-"
;;
-*)
echo "Unrecognized option: $1" 1>&2
usage 1
;;
0)
# CTID 0, i.e. host system, vzlist doesn't know it
CTIDS=$(echo $CTIDS 0)
;;
*)
# A file name or CT id/name? Check CT first
ID=$(vzlist -H -o ctid "$1" 2>/dev/null)
if test $? -eq 0; then
CTIDS=$(echo $CTIDS $ID)
else
FILE="$1"
fi
;;
esac
shift
done
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
# Cumulative mode preparations and sanity checks
if ! test -z "$cumulative"; then
# Create dir if needed
if ! test -d $STOREDIR; then
mkdir $STOREDIR || exit 1
fi
# Check we can write to it
touch $STOREDIR/test || exit 1
rm -f $STOREDIR/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 qheld="$Q_HELD" -v qmaxheld="$Q_MAXHELD" \
-v cumul="$cumulative" -v storedir="$STOREDIR" \
'
BEGIN {
if (qmaxheld > qheld)
qmaxheld=qheld
}
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("%.2f", p / d * 100);
fmt="%d"
if (r < 10)
fmt="%.2g"
r = sprintf(fmt, r)
if (r == 0)
return "- "
return r "%"
}
function important(id, held, maxheld, barrier, limit, failcnt) {
if (failcnt > 0)
return 2;
if (barrier == 0)
barrier = limit;
if ((barrier == 9223372036854775807) || (barrier == 2147483647))
return 0;
if (held > barrier)
return 2;
if (held > barrier * qheld)
return 1;
if (maxheld > barrier * qmaxheld)
return 1;
return 0;
}
/^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=strtonum($1)
if ((CTIDS !~ /^[ ]*$/) && (CTIDS !~ " " bcid " ")) {
bcid=""
next
}
header=sprintf( \
"-------------------------------------------------------------------\n" \
"CT %-10s| HELD Bar%% Lim%%| MAXH Bar%% Lim%%| BAR | LIM |%cFAIL\n" \
"-------------+-----------------+-----------------+-----+-----+-----",
bcid, cumul ? "+" : " ")
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!="") && (bcid!="")) {
imp=important(id, held, maxheld, barrier, limit, failcnt)
if (quiet)
if (!imp) {
id=""
next
}
if (header != "") {
if (printed)
print ""
print header
printed=1
header=""
}
newfc=failcnt
if ( (cumul) && (failcnt > 0) ) {
storefile=storedir "/" bcid "." id
getline oldfc < storefile
if (oldfc > 0)
failcnt=failcnt-oldfc
if (failcnt < 0)
failcnt=newfc
}
printf "%13s|%5s %5s %5s|%5s %5s %5s|%5s|%5s| %5s\n",
id,
hr(id, held), dp(held, barrier), dp(held, limit),
hr(id, maxheld), dp(maxheld, barrier), dp(maxheld, limit),
hr(id, barrier), hr(id, limit), hr(id, failcnt)
# Save failcnt to store file
if ( (cumul) && (newfc > 0) )
print newfc > storefile
id=""
}
END {
if (printed)
printf "-------------------------------------------------------------------\n"
}
'