Zabbix: Postfix statistical graphs using passive checks

We run a Postfix mailserver which we are monitoring with Zabbix. As Zabbix can generate nice graphs, why not add some statistical graphs for Postfix?

After a bit of searching, I found this nice howto.

The problem is that this way uses active checks (client sends data to the Zabbix server) but we want passive checks (Zabbix server asks client for data).

Here is a way of doing exactly that:

Requirements:

  • pflogsumm & logtail
  • A shell script on the server running Postfix which returns us some useful numbers
  • Zabbix agent with Remote commands enabled
  • The corresponding Zabbix templates
  • A cronjob to update the stats file

zabbix_agentd.conf on the client needs the following lines:

EnableRemoteCommands=1
UserParameter=postfix.pfmailq,mailq | grep -v "Mail queue is empty" | grep -c '^[0-9A-Z]'
UserParameter=postfix[*],/usr/local/bin/postfix-zabbix-stats.bash $1

postfix-zabbix-stats.bash goes to /usr/local/bin on the server where Postfix is doing its work:

#!/usr/bin/env bash

MAILLOG=/var/log/mail.log
PFOFFSETFILE=/tmp/zabbix-postfix-offset.dat
PFSTATSFILE=/tmp/postfix_statsfile.dat
TEMPFILE=$(mktemp)
PFLOGSUMM=/usr/sbin/pflogsumm
LOGTAIL=/usr/sbin/logtail

PFVALS=( 'received' 'delivered' 'forwarded' 'deferred' 'bounced' 'rejected' 'held' 'discarded' 'reject_warnings' 'bytes_received' 'bytes_delivered' )

[ ! -e "${PFSTATSFILE}" ] && touch "${PFSTATSFILE}" && chown zabbix:zabbix "${PFSTATSFILE}"

printvalues() {
  key=$1
  pfkey=$(echo "$1" | tr '_' ' ')
  value=$(grep -m 1 "${pfkey}" $TEMPFILE | awk '{print $1}' | awk '/k|m/{p = /k/?1:2}{printf "%d\n", int($1) * 1024 ^ p}')
  old_value=$(grep -e "^${key};" "${PFSTATSFILE}" | cut -d ";" -f2)
  if [ -n "${old_value}" ]; then
    sed -i -e "s/^${key};${old_value}/${key};$((${old_value}+${value}))/" "${PFSTATSFILE}"
  else
    echo "${key};${value}" >> "${PFSTATSFILE}"
  fi
}

if [ -n "$1" ]; then 
  key=$(echo ${PFVALS[@]} | grep -wo $1)
  if [ -n "${key}" ]; then
    value=$(grep -e "^${key};" "${PFSTATSFILE}" | cut -d ";" -f2)
    echo "${value}"
  else
    rm "${TEMPFILE}"
    exit 2
  fi
else
  "${LOGTAIL}" -f"${MAILLOG}" -o"${PFOFFSETFILE}" | "${PFLOGSUMM}" -h 0 -u 0 --bounce_detail=0 --deferral_detail=0 --reject_detail=0 --no_no_msg_size --smtpd_warning_detail=0 > "${TEMPFILE}"
  for i in "${PFVALS[@]}"; do
    printvalues "$i"
  done
fi

rm "${TEMPFILE}"

crontab entry to periodically update the stats file:

*/5 * * * * /usr/local/bin/postfix-zabbix-stats.bash

Now import the template in Zabbix and add your Postfix server to it. The file adds two templates, one for General SMTP monitoring (reachability of port 25/smtp) and one called “App Postfix” for monitoring Postfix: traffic in/out, number of  received/delivered/forwarded/deferred/bounced/rejected/held/discarded/reject_warnings mails, the mail queue and two graphs.

The check interval can be any number as the values are processed as deltas, the conjob should run more often than the check interval, but beware of setting it too low as pflogsumm needs some time to process the logs (especially if you have a high volume mailserver with big logs).

Feel free to improve or edit the files to suit your needs. It surely can be done in a more beatiful way but for us it does the job very well.

Download:

Thank to toerb for his help!

By the way: I am really impressed with the performance increase in Zabbix versions 2.2+.



Facebooktwittergoogle_plusredditpinterestlinkedinmail

8 comments

  • rubrix

    Nice tips!

    The script postfix-zabbix-stats.bash should be recalled by cron or zabbix every x sec without parameters in order to update stats and offset files. Where do you run it?

  • luzem

    the last line of postfix-zabbix-stats.bash differs between post and download file.

    last line
    rm “${TEMPFILE}”

  • Ruud Baart

    Thank for the script. I made some minor improvements. Nothing special

    #!/bin/bash

    # change to -x to turn on tracing
    set +x

    MAILLOG=/var/log/mail.log
    PFOFFSETFILE=/tmp/zabbix-postfix-offset.dat
    PFSTATSFILE=/tmp/postfix_statsfile.dat
    TEMPFILE=$(mktemp)
    PFLOGSUMM=/usr/sbin/pflogsumm
    LOGTAIL=/usr/sbin/logtail
    # change to 0 to turn of debugging
    DEBUG=1
    # trap interrupts
    trap “rm -f ${TEMPFILE}; exit 1” 2 3 15

    if [ ! -x “${PFLOGSUMM}” ] ; then
    echo “${PFLOGSUMM} not found”
    exit 1
    fi
    if [ ! -x “${LOGTAIL}” ] ; then
    echo “${LOGTAIL} not found”
    exit 1
    fi
    if [ ! -r “${MAILLOG}” ] ; then
    echo “${MAILLOG} not readable”
    exit 1
    fi

    PFVALS=( ‘received’ ‘delivered’ ‘forwarded’ ‘deferred’ ‘bounced’ ‘rejected’ ‘held’ ‘discarded’ ‘reject_warnings’ ‘bytes_received’ ‘bytes_delivered’ )

    [ ! -e “${PFSTATSFILE}” ] && touch “${PFSTATSFILE}” && chown zabbix:zabbix “${PFSTATSFILE}”

    printvalues() {
    key=$1
    pfkey=$(echo “$1” | tr ‘_’ ‘ ‘)
    value=$(grep -m 1 “${pfkey}” $TEMPFILE | awk ‘{print $1}’ | awk ‘/k|m/{p = /k/?1:2}{printf “%d\n”, int($1) * 1024 ^ p}’)
    old_value=$(grep -e “^${key};” “${PFSTATSFILE}” | cut -d “;” -f2)
    if [ -n “${old_value}” ]; then
    sed -i -e “s/^${key};${old_value}/${key};$((${old_value}+${value}))/” “${PFSTATSFILE}”
    else
    echo “${key};${value}” >> “${PFSTATSFILE}”
    fi
    [ $DEBUG -eq 1 ] && echo “key=${key} value=${value}”
    }

    if [ -n “$1” ]; then
    key=$(echo ${PFVALS[@]} | grep -wo $1)
    if [ -n “${key}” ]; then
    [ $DEBUG -eq 1 ] && echo “First argument $1 is a valid key”
    value=$(grep -e “^${key};” “${PFSTATSFILE}” | cut -d “;” -f2)
    echo “${value}”
    else
    [ $DEBUG -eq 1 ] && echo “First argument $1 is not a valid key”
    rm “${TEMPFILE}”
    exit 2
    fi
    else
    “${LOGTAIL}” -f”${MAILLOG}” -o”${PFOFFSETFILE}” | “${PFLOGSUMM}” -h 0 -u 0 –bounce_detail=0 –deferral_detail=0 –reject_detail=0 –no_no_msg_size –smtpd_warn
    ing_detail=0 > “${TEMPFILE}”
    for i in “${PFVALS[@]}”; do
    printvalues “$i”
    done
    fi

    rm “${TEMPFILE}”

  • Ruud Baart

    Made a little mistake. The checks are only allowed if root is running the job, not when zabbix is requesting data.

    if [ $# -eq 0 ] ; then # root is doing the cron job, in case of zabbix $# -eq 1
    if [ ! -x “${PFLOGSUMM}” ] ; then
    echo “${PFLOGSUMM} not found”
    exit 1
    fi
    if [ ! -x “${LOGTAIL}” ] ; then
    echo “${LOGTAIL} not found”
    exit 1
    fi
    if [ ! -r “${MAILLOG}” ] ; then
    echo “${MAILLOG} not readable”
    exit 1
    fi
    fi

Leave a Reply

Your email address will not be published. Required fields are marked *