| #!/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 ? "-------" : "" |
| } |
| ' |