Repository
Munin (contrib)
Last change
2021-07-16
Graph Categories
Family
auto
Capabilities
Keywords
Language
Python (3.x)
License
GPL-2.0-only
Authors

amavis_multi

Name

amavis_multi - monitor amavis mail filter status

Configuration

Following config is needed:

[amavis_multi]
user amavis

Author

Kim Heino b@bbbs.net

License

GPLv2

Magic Markers

#%# family=auto
#%# capabilities=autoconf
#!/usr/bin/env python3

"""Munin plugin to monitor amavis mail filter status.

=head1 NAME

amavis_multi - monitor amavis mail filter status

=head1 CONFIGURATION

Following config is needed:

    [amavis_multi]
    user amavis

=head1 AUTHOR

Kim Heino <b@bbbs.net>

=head1 LICENSE

GPLv2

=head1 MAGIC MARKERS

 #%# family=auto
 #%# capabilities=autoconf

=cut
"""

import os
import subprocess
import sys


def run_binary(arg):
    """Run binary and return output."""
    try:
        return subprocess.run(arg, stdout=subprocess.PIPE,
                              stderr=subprocess.STDOUT, check=False,
                              encoding='utf-8', errors='ignore').stdout
    except FileNotFoundError:
        return ''


def get_values():
    """Get status from nanny and agent. They are part of amavis."""
    ret = {
        'nanny': {
            'total': 0,
            'busy': 0,
        },
        'agent': {},
    }
    agent = run_binary(['amavisd-agent', '-c', '1'])
    nanny = run_binary(['amavisd-nanny', '-c', '1'])
    if not agent or not nanny:
        return ret

    # Busy count from nanny
    for line in nanny.splitlines():
        if not line.startswith('PID ') or ':' not in line:
            continue
        ret['nanny']['total'] += 1
        if ':   ' not in line:
            ret['nanny']['busy'] += 1

    # Mail counts and processing times from agent
    for line in agent.splitlines():
        items = line.split()
        if len(items) > 1 and items[1].isnumeric():
            ret['agent'][items[0]] = items[1:]
    return ret


def print_status(config):
    """Print config or values."""
    # pylint: disable=too-many-branches
    # pylint: disable=too-many-statements
    both = os.environ.get('MUNIN_CAP_DIRTYCONFIG') == '1'
    values = get_values()

    # Busy processes. Use average of last three runs so that single "bad
    # timing" doesn't trigger warning.
    print('multigraph amavis_processes')
    if config or both:
        print('graph_title Amavis mail filter busy processes')
        print('graph_vlabel % busy')
        print('graph_args --base 1000 --lower-limit 0 --upper-limit 100')
        print('graph_category spamfilter')
        print('busy.label Percent of busy processes')
        print('busy.warning 80')
        print('busy.critical 95')
    if not config or both:
        if not values['nanny']['total']:
            print('busy.value U')
        else:
            statename = os.path.join(os.getenv('MUNIN_PLUGSTATE'),
                                     'amavis_multi.state')
            try:
                with open(statename, 'rt') as statef:
                    count1 = int(statef.readline())
                    count2 = int(statef.readline())
            except (FileNotFoundError, TypeError, ValueError):
                count1 = count2 = 0
            with open(statename, 'wt') as statef:
                print(values['nanny']['busy'], file=statef)
                print(count1, file=statef)
            print('busy.value {}'.format(
                (values['nanny']['busy'] + count1 + count2) * 100 / 3 /
                values['nanny']['total']))

    # Mail counts
    print('multigraph amavis_mails')
    if config or both:
        print('graph_title Amavis mail filter mail counts')
        print('graph_period minute')
        print('graph_vlabel mails per ${graph_period}')
        print('graph_args --base 1000 --lower-limit 0')
        print('graph_category spamfilter')
        print('contentcleanmsgs.label Clean mails')
        print('contentcleanmsgs.type DERIVE')
        print('contentcleanmsgs.min 0')
        print('contentbadhdrmsgs.label Bad header mails')
        print('contentbadhdrmsgs.type DERIVE')
        print('contentbadhdrmsgs.min 0')
        print('contentspammymsgs.label Spammy mails')
        print('contentspammymsgs.type DERIVE')
        print('contentspammymsgs.min 0')
        print('contentspammsgs.label Spam mails')
        print('contentspammsgs.type DERIVE')
        print('contentspammsgs.min 0')
        print('contentvirusmsgs.label Virus mails')
        print('contentvirusmsgs.type DERIVE')
        print('contentvirusmsgs.min 0')
        print('inmsgs.label Total mails')
        print('inmsgs.type DERIVE')
        print('inmsgs.min 0')
    if not config or both:
        for key in ('ContentCleanMsgs',
                    'ContentBadHdrMsgs',
                    'ContentSpammyMsgs',
                    'ContentSpamMsgs',
                    'ContentVirusMsgs',
                    'InMsgs'):
            if key in values['agent']:
                print('{}.value {}'.format(key.lower(),
                                           values['agent'][key][0]))
            else:
                # Key is missing if no such mail yet, so 0, not U.
                print('{}.value 0'.format(key.lower()))

    # Elapsed times
    print('multigraph amavis_times')
    if config or both:
        print('graph_title Amavis mail filter elapsed times')
        print('graph_vlabel seconds per mail')
        print('graph_args --base 1000 --lower-limit 0')
        print('graph_category spamfilter')
        print('timeelapsedreceiving.label Receiving')
        print('timeelapseddecoding.label Decoding')
        print('timeelapsedspamcheck.label Spam check')
        print('timeelapsedviruscheck.label Virus check')
        print('timeelapsedsending.label Sending')
        print('timeelapsedtotal.label Total')
    if not config or both:
        for key in ('TimeElapsedReceiving',
                    'TimeElapsedDecoding',
                    'TimeElapsedSpamCheck',
                    'TimeElapsedVirusCheck',
                    'TimeElapsedSending',
                    'TimeElapsedTotal'):
            if key in values['agent']:
                print('{}.value {}'.format(key.lower(),
                                           values['agent'][key][2]))
            else:
                # Key is missing if feature is not used, so 0, not U.
                print('{}.value 0'.format(key.lower()))


if __name__ == '__main__':
    if len(sys.argv) > 1 and sys.argv[1] == 'autoconf':
        print('yes' if get_values()['agent'] else
              'no (amavis is not running)')
    elif len(sys.argv) > 1 and sys.argv[1] == 'config':
        print_status(True)
    else:
        print_status(False)