#!/bin/bash : << =cut =head1 NAME netatalk - Plugin to monitor number of files held open by the Netatalk/AFP clients =head1 CONFIGURATION This plugin uses the following configuration variables [netatalk] user root - Plugin must run under root for lsof env.afpd - Path to "afpd" executable (defaults to what afpd -v says, usually "/usr/sbin/afpd") env.afpdconf - Path to "afpd.conf" file (defaults to what afpd -v says, usually "/etc/netatalk/afpd.conf") =head1 VERSION 0.1 (2012-05-23) =head1 AUTHOR DUVERGIER Claude (http://claude.duvergier.fr) =head1 LICENSE GPLv2 =head1 BUGS Known bugs: * A bug can occur if a shared path is located inside another shared path (eg. "/mnt/afpShares/foo" and "/mnt/afpShares/foo/subFoo/bar"). Depending of the order the shares are found, the script might say the less-deeper one is opened whereas he is processing the other one. =head1 TODO Features to-do list: * Support multiple servers =head1 MAGIC MARKERS #%# family=contrib #%# capabilities=autoconf =cut # Find "afpd" location: afpdPath=${afpd:-`which afpd`} afpdPath=${afpdPath:-/usr/sbin/afpd} ##### ## Munin configuration ("autoconf" and "config") ##### if [ "$1" = "autoconf" ]; then if [ -x $afpdPath ]; then echo yes exit 0 else echo no '(afpd not found)' exit 0 fi fi if [ "$1" = "config" ]; then echo 'graph_title Netatalk status' echo 'graph_args --logarithmic --lower-limit 0.1' echo 'graph_vlabel Number' echo 'graph_category network' echo 'proc.label Running processes' echo 'proc.info Number of running afpd processes' echo 'proc.min 0' echo 'user.label Connected users' echo 'user.info Number of users connected to netatalk service' echo 'user.min 0' echo 'lock.label Locked files' echo 'lock.info Number of files locked by afpd' echo 'lock.min 0' echo 'share.label Open shares' echo 'share.info Number of open netatalk shares' echo 'share.min 0' exit 0 fi ########## ##### ## Script environment ##### # Find "afpd.conf" location: afpdConfPath=${afpdconf:-`$afpdPath -v 2>/dev/null | grep "afpd.conf" | awk '{print $2}'`} baseLsofCommand="lsof -a -c $(basename $afpdPath) -d ^DEL,^err,^jld,^ltx,^Mxx,^m86,^mem,^mmap,^pd,^rtd,^tr,^txt,^v86" #TODO: Support multiple servers defaultServer_defaultVolConfigLine=$(cat $afpdConfPath | grep "^[^#]" | grep "\-defaultvol") if [ -z "$defaultServer_defaultVolConfigLine" ]; then defaultServer_volumesFile=`$afpdPath -v 2>/dev/null | grep "AppleVolumes.default" | awk '{print $2}'` # Default location "AppleVolumes.default" else defaultVol_pathIsQuoted="-defaultvol ('|\")" defaultVol_quotedPattern="-defaultvol ('|\")([^\1]*)\1" # Following regex is from http://stackoverflow.com/questions/537772/what-is-the-most-correct-regular-expression-for-a-unix-file-path defaultVol_nonQuotedPattern='-defaultvo(l) (([^\0 !$`&*()+]|\\( |!|$|`|&|\*|\(|\)|\+))+)' defaultVol_pathPattern=$defaultVol_nonQuotedPattern [[ $defaultServer_defaultVolConfigLine =~ $defaultVol_pathIsQuoted ]] && defaultVol_pathPattern=$defaultVol_quotedPattern [[ $defaultServer_defaultVolConfigLine =~ $defaultVol_pathPattern ]] && defaultServer_volumesFile=${BASH_REMATCH[2]} fi ########## ##### ## Actual monitoring ##### # Running processes (proc): echo "proc.value" $(ps ax --no-headers -o command | grep "^$afpdPath" | wc -l) # Connected users (user): # We will ignore root (having UID=0 it's line will be first) (assomption done: there will have only one line corresponding to root in `ps` output) connectedUsers=$(ps anx --no-headers -o uid,command | sed 's/^ *//g' | grep "^[0-9]* $afpdPath" | sort -n | tail -n +2 | awk '{print $1}') echo "user.value" `echo $connectedUsers | wc -w` # Locked files (lock): echo "lock.value" $($baseLsofCommand -F l | grep "^l[rRwWu]" | wc -l) # Open shares (share): openShares=0 #TOFIX: A bug can occur if a shared path is located inside another shared path (eg. "/mnt/afpShares/foo" and "/mnt/afpShares/foo/subFoo/bar"): # Depending of the order the shares are found, the script might say the less-deeper one is opened whereas he is processing the other one # Solution: sort shares per descending depth # Following regex is from http://stackoverflow.com/questions/10134129/generic-shell-bash-method-to-parse-string-for-possibly-quoted-fields for shareName in `cat $defaultServer_volumesFile | grep "^[^#:]" | grep -oP "^(['\"]).*?\1|^(\\\\ |[^ '\"])*"`; do if [[ "$shareName" =~ "~" ]]; then for currentUid in $connectedUsers; do # For each connected users currentUserHomeDir=`getent passwd $currentUid | cut -d ':' -f6` # Fetch it's the home directory currentUserHomeDir=`readlink -f "$currentUserHomeDir"` # We want the realpath (resolves symbolic links and normalize the path) #FIX: We use pipe `lsof` outputs to `echo -e` with `xargs` because lsof "displays only printable ASCII characters" (cf. http://ftp.cerias.purdue.edu/pub/tools/unix/sysutils/lsof/00FAQ #14.5) # Then if a share with non-ASCII characters in it's path were to be opened, lsof would return them on \xNN form and grep wouldn't match: `echo -e /the/path` fixes this [ `$baseLsofCommand -F n | xargs -0 echo -e | grep "^n$currentUserHomeDir" | wc -l` -gt 0 ] && let openShares++ # If found in lsof output: increment the openShares counter done else shareName=`readlink -f "$shareName"` # We want the realpath (resolves symbolic links and normalize the path) [ `$baseLsofCommand -F n | xargs -0 echo -e | grep "^n$shareName" | wc -l` -gt 0 ] && let openShares++ # If found in lsof output: increment the openShares counter fi done echo "share.value" $openShares ##########