This is nxfaxsummary.sh in view mode; [Download] [Up]
#!/bin/sh -
#
# NXFax accounting 1.0 - a gawk script to summarize NXFax usage
# by Andreas Mueller [afm@mathi.uni-heidelberg.de] and
# Adrian Steinmann [ast@lia.di.epfl.ch]
#
#
PATH=/bin:/usr/bin:/usr/ucb:/etc:/usr/etc
IFS='
'
export PATH IFS
#
TRIMDAYS=30 # how many days to wait between trimming
KEEPLINES=200 # how many lines to inherit when trimming
#
# define LOGFILE here, possibly override later
#
# NXFax 1.03 uses /usr/adm/nxfax/nxfaxlog
NXFAXDIR=/usr/adm/nxfax
if [ ! -d ${NXFAXDIR} ]
then
# NXFax 1.02 uses /etc/nxfax/nxfaxlog
NXFAXDIR=/etc/nxfax
if [ ! -d ${NXFAXDIR} ]
then
echo "Fatal error: Cannot find NXFax log file directory" >&2
usage
fi
fi
#
LOGFILE=${NXFAXDIR}/nxfaxlog
#
# OLOGFILE is where we put the log file when we trim
#
OLOGFILE="${LOGFILE}.old"
#
# COSTFILE, usually in same directory as logfile, possibly override below
#
: ${COSTFILE:=${NXFAXDIR}/nxfax.cost}
#
# we use GNU awk for builtin and custom functions
#
GAWK=/usr/local/bin/gawk
#
# this is what we say as a heading and subject line in case of mail (-m option)
#
WELCOME="NXFax Accounting @ `hostname`"
#
# CMD may be changed to "Mail -s '${WELCOME}' $recipients" below (-m option)
#
CMD=cat
#
export CMD WELCOME GAWK COSTFILE LOGFILE OLOGFILE NXFAXDIR KEEPLINES TRIMDAYS
#
# temporary files are cleanup up on exit or trap
#
TMPFHED=/tmp/faxhed$$
TMPFSND=/tmp/faxsnd$$
TMPFCST=/tmp/faxcst$$
TMPFPAR=/tmp/faxcst$$
TMPFRCV=/tmp/faxrcv$$
TMPFSUM=/tmp/faxsum$$
#
export TMPFHED TMPFSND TMPFCST TMPFPAR TMPFRCV TMPFSUM
#
umask 077
#
help ()
{
echo
cat<<!!
This shell script uses GNU-awk '$GAWK' to
parse log files generated by NXFax versions 1.02 and 1.03. By
default, the script will search for a NXFax log file in the
directories "/etc/nxfax/" and "/usr/adm/nxfax/". It will
also look for a file called "nxfax.cost" in that directory
describing the calling costs for various times and phone
prefixes. For an example cost description file, invoke this
script with the option "-sample". If the cost description file
is missing, a simple grand total summary is calculated.
Alternate cost description or NXFax log files can be specified
via the -c and -l options respectively. With the option -m
<address_list>, the summary is sent to the recipients
specified in address_list.
!!
echo
usage
}
#
usage ()
{
echo "Usage: $0 [ -help | -sample ]" >&2
echo " $0 [ [-l] <log> ] [ -c <costs> ] [ -m {address_list} ]" >&2
exit 1
}
#
cleanup ()
{
rm -f ${TMPFHED} ${TMPFSND} ${TMPFPAR} ${TMPFCST} ${TMPFRCV} ${TMPFSUM}
}
#
# note that NXFax 1.02 and 1.03 trims itself to 1000 lines at startup time
#
trimlog ()
{
if [ -f ${OLOGFILE} ]
then
(find ${OLOGFILE} -mtime +${TRIMDAYS} -print |
grep ${OLOGFILE} ) > /dev/null 2>&1
if [ $? -eq 0 ]
then
( cp -p ${LOGFILE} ${OLOGFILE} &&
tail -${KEEPLINES} ${LOGFILE}.old > ${LOGFILE} &&
echo "${LOGFILE} rotated to ${OLOGFILE}, ${KEEPLINES} lines kept"
) 2> /dev/null
fi
else
# if the old file doesn't exist, create it today so that
# trimlog() will begin to rotate after ${TRIMDAYS}
touch ${OLOGFILE}
fi
}
#
sample ()
{
cat<<!!
# Sample cost desciption file: '${NXFAXDIR}/nxfax.cost'
#
# This file is used by the nxfaxsummary shell script to compute the
# approximate costs of outgoing fax traffic. Its precision depends
# highly on the amount of detail given in this file.
#
# FORMAT DESCRIPTION - there are three types of lines:
#
# $<currency-string>
# :<prefix>:<unit-time>:<unit-cost>:<connect-cost>:<comments>
# +<prefix>:<comments>
#
# The '$' line defines a currency string to be used in the summary.
# Lines beginning with ':' define cost computation parameters.
# Lines beginning with '+' are called summary lines and are used
# to indicate how costs should be summarized by nxfaxsummary.sh
#
# The script scans the called phone number and uses the parameters
# associated with the first matching <prefix> to compute the costs
# for such a call. If t is the connect time in seconds, then the
# cost is computed according to the formula
#
# cost = connect-cost + (t/unit-time)*unit-cost
#
# connect-cost is the minimum fee per connection (0 in most situations)
# this can be used to account for connect time measured
# by NXFax which is usually shorter than the effective
# duration of a call including initial handshake time;
# unit-time is the duration (in seconds) of a time unit;
# unit-cost is the price of one such unit-time.
#
# In the final summary, these computations are cumulatively grouped
# according to the prefixes in the summary lines (the '+' lines).
#
# Empty lines and lines beginning with a '#' are ignored.
#
# EXAMPLE COST DESCRIPTION (for Germany)
\$DM
:0041:0:12:0.23:Calls to Switzerland
:00:0:12:0.23:Other international calls
+00:International calls:
:0:0:60:0.23:Long distance calls within Germany (Regionalzone)
::0:360:0.23:Local calls
+:Calls within Germany:
#
# The cost description file should always include a line beginning
# with an empty prefix string, this is used as the default for phone
# numbers that don't match any other prefix. The same is true for
# summary lines. Note that the comments of the summary lines are
# used as line item titles, hence they are often ended with a ':'.
#
# In other words, a minimum cost file may consist of just three lines:
#
# \$\$
# ::0:3600:1.20:Detailed cost description
# +:Detailed summary description:
#
# If no cost description file is found nor specified, then a simple total
# transmit time and average per page is output.
#
# Most phone companies charge differently during and after business hours
# - use the parameters that are likely to be valid when *you* send faxes.
#
# To be done: - differentiate between rates during different time periods
#
!!
exit 0
}
#
trap 'cleanup; exit 9' 2 3 15
#
# parse options - default is output to stdout (use -m option to mail)
#
dorecipients=0
docost=0
dolog=0
while [ $# -gt 0 ]
do
while [ $dorecipients -ge 1 -a $# -gt 0 ]
do
dorecipients=2
case x$1 in
x-*)
break
;;
*)
recipients="$recipients $1"
CMD="Mail -s '${WELCOME}' $recipients"
shift
;;
esac
done
if [ $dorecipients -eq 2 -a `echo $recipients | wc -w` -eq 0 ]
then
echo "Fatal error: -m option lacks at least one addressee" >&2
usage
else
dorecipients=0
fi
if [ $docost -eq 1 -o $dolog -eq 1 ]
then
if [ $# -gt 0 ]
then
if [ ! -r $1 ]
then echo "Fatal error: file '$1' unreadable" >&2
usage
else
if [ $dolog -eq 1 ]
then
LOGFILE=$1
dolog=2
else
COSTFILE=$1
docost=2
fi
fi
shift
else
echo "Fatal error: option lacks an argument" >&2
usage
fi
fi
case x$1 in
x-m)
shift
if [ $dorecipients -ne 0 -o $# -lt 1 ]; then usage; fi
dorecipients=1
;;
x-c)
if [ $docost -ne 0 ]; then usage; fi
shift; docost=1
;;
x-l)
if [ $dolog -ne 0 ]; then usage; fi
shift; dolog=1
;;
x-h*)
help
;;
x-s*)
sample
;;
x-*)
usage
;;
*)
dolog=1
;;
esac
done
#
if [ ! -r $LOGFILE ]
then
echo "Fatal error: Log file '${LOGFILE}' unreadable" >&2
usage
fi
OLOGFILE="${LOGFILE}.old"
#
# Finally do some real work: find first and last date in logfile
#
( echo -n "\n${WELCOME} "
${GAWK} '
BEGIN { found=1; }
$3 ~ /Sun|Mon|Tue|Wed|Thu|Fri|Sat/ \
{ date=sprintf("%2d %s %d %s",$5,$4,$7,$6);
if (found) { printf("from %s ",date)
found=0 }
}
END { printf("to %s\n",date); }' ${LOGFILE}
) > ${TMPFHED} 2>&1
#
# now search logfile for faxes sent, collect information in ${TMPFSND}
#
${GAWK} \
'function resetvars() { connected=false; call=false
speed="?"; res="?"; errors="?"; number="?"
ctime="?"; ttime="?"; pages=0
date=" ? ? ? "; calldate=date
}
function printsummary() { printf("%16s %-20s %3s %2s %4s %5s %5s %s\n",
calldate,number,pages,substr(res,1,2),
speed,
ttime,ctime,errors)
}
BEGIN { true=1; false=0; resetvars() }
$3 ~ /(Sun)|(Mon)|(Tue)|(Wed)|(Thu)|(Fri)|(Sat)/ \
{ date=sprintf("%2d %s %s",$5,$4,$6) }
# Note that NXFax 1.03 frequently contains the line
# "%> NXFax: caller id or silent answer forces rings to 3"
$3 ~ /call/ { if ($4 !~ /complete/ && $8 !~ /forces/ ) {
call=true; number=substr($0,15,20)
}
}
$3 ~ /line/ { if ($4 ~ /busy/) { calldate=date } }
$3 ~ /Connected/ { if (call==true) { connected=true; calldate=date } }
$4 ~ /resolution/ { res=$3 }
$4 ~ /bps/ { speed=sprintf("%4.1f",$3/1000) }
$3 ~ /page/ { pages=$4 }
$3 ~ /hangup/ { if ( substr($3, 8, 1) == "0" )
{ errors=$4" "$5 }
else
{ errors="Hangup ERROR" }
}
/connect time/ { ctime=$5 }
/transmit time/ { ttime=$5
if (connected) {
printsummary()
}
resetvars()
}
$4 ~ /halt|busy/ { if (call) {
printsummary()
resetvars()
}
}
/no carrier/ { if (call) {
calldate=date; errors="no carrier"
printsummary()
resetvars()
}
}
' ${LOGFILE} > ${TMPFSND} 2>&1
#
#
# use the information collected in the previous step to get a
# more accurate cost computation:
#
if [ ! -r $COSTFILE ]
then
awk '{ print substr($0,53,5)" "substr($0,39,3)" "substr($0,19,20) }' \
${TMPFSND} | ${GAWK} '
BEGIN { transmit=0; pages=0; }
$1 !~ /\?/ { pages+=$2; split($1,ttime,":")
transmit+=60*ttime[1]+ttime[2] }
END {
time_h=int(transmit/3600); time_s=transmit-3600*time_h;
printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
time_h,time_s/60,time_s%60,pages)
if (pages>0) printf("\t~ %8.2f sec/page",transmit/pages)
printf("\n")
}
' > ${TMPFCST} 2>&1
else
awk '{ print substr($0,53,5)" "substr($0,39,3)" "substr($0,60,5)\
" "substr($0,19,20) }' ${TMPFSND} \
| ${GAWK} -v costparamfile=${COSTFILE} '
BEGIN { # initialize parameter arrays
"sed -n -e s/\\^\\\\$//p "costparamfile | getline currency
close("sed -n -e s/\\^\\\\$//p "costparamfile)
FS=":"; counter=0;
while ("sed -n -e s/\\^://p "costparamfile | getline ) {
counter++
costp[counter,"prefix"]=$1
costp[counter,"connect"]=$2
costp[counter,"tunit"]=$3
costp[counter,"unitc"]=$4
}
close("sed -n -e s/\\^://p "costparamfile)
max=counter
s=0
while ("sed -n -e s/\\^\\+//p "costparamfile | getline) {
s++
summa[s,"prefix"]=$1
summa[s,"comment"]=$2
for (i=3; i<=NF; i++)
summa[s,"comment"]=summa[s,"comment"]":"$i
}
summax=s; FS=" "
transmit=0; pages=0;
}
$2 !~ /\?/ { pages+=$2; split($1,ttime,":")
transmit+=60*ttime[1]+ttime[2] }
$4 !~ /\?/ {# on every line where you have number and connect time,
# do the following
if ( $3 !~ /\?/ ) {
split($3,timing,":"); time=60*timing[1]+timing[2];
counter=1; found=0;
while (counter<=max && !found) {
if ( index($4,costp[counter,"prefix"])==1 ) {
found=1
cost[counter,"time"]+=time
cost[counter,"cost"]+=(costp[counter,"connect"]+ \
time*costp[counter,"unitc"]/costp[counter,"tunit"])
} else if ( length(costp[counter,"prefix"])==0 ) {
cost[counter,"time"]+=time
cost[counter,"cost"]+=(costp[counter,"connect"]+ \
time*costp[counter,"unitc"]/costp[counter,"tunit"])
}
counter++
}
}
}
END {
for (counter=1; counter<=max; counter++) {
s=1; found=0;
while (s<=summax && !found) {
if ( index(costp[counter,"prefix"],summa[s,"prefix"])==1 ) {
found=1
summa[s,"time"]+=cost[counter,"time"]
summa[s,"cost"]+=cost[counter,"cost"]
} else if ( length(summa[s,"prefix"])==0 ) {
found=1
summa[s,"time"]+=cost[counter,"time"]
summa[s,"cost"]+=cost[counter,"cost"]
}
s++
}
}
# output collected information
for (s=1; s<=summax; s++) {
time_h=int(summa[s,"time"]/3600);
time_s=summa[s,"time"]-3600*time_h;
printf("%-45s\t%2d:%02d:%02d ~%7.2f %s\n", \
summa[s,"comment"],time_h,time_s/60,time_s%60, \
summa[s,"cost"],currency)
totalcost+=summa[s,"cost"]
}
printf("%-45s\t\t %8.2f %s\n","Total cost:", totalcost, \
currency)
time_h=int(transmit/3600); time_s=transmit-3600*time_h;
printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
time_h,time_s/60,time_s%60,pages)
if (pages>0) printf("\t~ %8.2f sec/page",transmit/pages)
printf("\n")
}
' > ${TMPFCST} 2>&1
fi
#
# now search logfile for faxes received, collect information in ${TMPFRCV}
#
${GAWK} '
function resetvars () { connected=false; call=false;
speed="?"; res="?"; errors="?"; number="?"
ctime="?"; ttime="?"; pages=0;
calldate=" ? ? ? "
}
BEGIN { true=1; false=0; resetvars(); t_ttime=0 }
$3 ~ /Sun|Mon|Tue|Wed|Thu|Fri|Sat/ \
{ date=sprintf("%2d %s %s",$5,$4,$6) }
# this might be special to NXFax 1.02
/%> NXFax: incoming call/ { call=true; calldate=date }
/%> NXFax: fax call/ { if (call) connected=true }
# this might be special to NXFax 1.03
/%> NXFax: from/ { if (connected) {
calldate=date
number=substr($0,15,20) }
}
$4 ~ /halt/ { connected=false; call=false }
$4 ~ /resolution/ { res=$3 }
$4 ~ /bps/ { speed=sprintf("%4.1f",$3/1000) }
$3 ~ /page/ { pages=$4 }
$3 ~ /hangup/ { if ( substr($3, 8, 1) == 0 )
{ errors=$4" "$5 }
else
{ errors="Hangup ERROR" }
}
/transmit time/ { ttime=$5
if (connected) {
if (number=="call") { number="?" }
printf("%16s %-20s %3s %2s %s %5s %s\n",
calldate,number,pages,substr(res,1,2),
speed,ttime,errors)
split(ttime,ttimecomp,":")
t_ttime+=ttimecomp[1]*60+ttimecomp[2]
t_pages+=pages
}
resetvars()
}
# this can happen on an error like
# %> NXFax: hangup[70]: Unspecified receive Phase B error.
/call complete/ { if (connected) {
printf("%16s %-20s %3s %2s %s %5s %s\n",
calldate,number,pages,substr(res,1,2),
speed,ttime,errors)
split(ttime,ttimecomp,":")
t_ttime+=ttimecomp[1]*60+ttimecomp[2]
t_pages+=pages
}
resetvars()
}
END {
t_th=int(t_ttime/3600); t_ttime-=3600*t_th;
printf("\nTotal transmit time: %3d:%02d:%02d for %d pages",\
t_th,t_ttime/60,t_ttime%60,t_pages);
if ( t_pages != 0) { printf("\t~ %8.2f sec/page\n",t_ttime/t_pages) }
printf("\n")
}' ${LOGFILE} > ${TMPFRCV} 2>&1
#
# Concatenate collected data
#
(
cat ${TMPFHED}
if [ `wc -l ${TMPFSND} | awk -e '{print $1}'` -gt 0 ]
then
echo "\n SEND FAX\n"
echo "Date Time Number pgs res kbps xmt conn errors\n"
cat ${TMPFSND}
if [ -r $COSTFILE ]
then
if [ `wc -l ${TMPFCST} | awk -e '{print $1}'` -gt 0 ]
then
echo "\nCost summary\t\t\t\t\t connect cost"
cat ${TMPFCST}
fi
else
cat ${TMPFCST}
fi
fi
if [ `wc -l ${TMPFRCV} | awk -e '{print $1}'` -gt 2 ]
then
echo "\n\n RECEIVE FAX\n"
echo "Date Time Number pgs res kbps xmt errors\n"
cat ${TMPFRCV}
fi
echo
) > ${TMPFSUM} 2>&1
#
if [ `wc -l ${TMPFSUM} | awk -e '{print $1}'` -gt 3 ]
then
cat ${TMPFSUM} | eval ${CMD}
fi
#
cleanup
#
exit 0
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.