]> jfr.im git - munin-plugins.git/blame - postfix_mailstats
support for a 2nd unbound plugin
[munin-plugins.git] / postfix_mailstats
CommitLineData
a8801b42
JR
1#!/usr/bin/perl -w
2# -*- perl -*-
3
4=head1 NAME
5
6postfix_mailstats - Plugin to monitor the number of mails delivered and
7rejected by postfix
8
9=head1 CONFIGURATION
10
11Configuration parameters for /etc/munin/postfix_mailstats,
12if you need to override the defaults below:
13
14 [postfix_mailstats]
15 env.logdir - Which logfile to use
16 env.logfile - What file to read in logdir
17
18=head2 DEFAULT CONFIGURATION
19
20 [postfix_mailstats]
21 env.logdir /var/log
22 env.logfile mail.log
23
24=head1 AUTHOR
25
26Records show that the plugin was contributed by Nicolai Langfeldt in
272003. Nicolai can't find anything in his email about this and expects
28the plugin is based on the corresponding exim plugin - to which it now
29bears no resemblence.
30
31=head1 LICENSE
32
33GPLv2
34
35=head1 MAGIC MARKERS
36
37=begin comment
38
39These magic markers are used by munin-node-configure when installing
40munin-node.
41
42=end comment
43
44 #%# family=manual
45 #%# capabilities=autoconf
46
47=head1 RANDOM COMMENTS
48
49Would be cool if someone ported this to Munin::Plugin for both state
50file and log tailing.
51
52=cut
53
54use strict;
55
56use Munin::Plugin;
57
58my $statefile = $ENV{'MUNIN_PLUGSTATE'} . "/munin-plugin-postfix_mailstats.state";
59my $pos;
26507458
JR
60my %descriptions = (
61 messages => 'Number of messages',
62 recipients => 'Number of recipients',
63 local => 'Outgoing local messages',
64 smtp => 'Outgoing SMTP messages',
65 pickup => 'Incoming local messages',
66 smtpd => 'Incoming SMTP messages',
67 bounce => 'Bounces handled',
68);
69my %stats = map { $_ => 0 } keys %descriptions;
a8801b42 70my %rejects = ();
26507458
JR
71
72if ( $ARGV[0] and $ARGV[0] eq "autoconf" ) {
73 my $logfile;
74 `which postconf >/dev/null 2>/dev/null`;
75 if (!$?) { # if postconf returns success
76 `journalctl --facility=mail --show-cursor -n 0`;
77 if (!$?) { # if journalctl returns success
78 print "yes\n";
79 } else {
80 print "no (journalctl returned error)\n";
81 }
82 } else {
83 print "no (postfix not found)\n";
84 }
85
86 exit 0;
a8801b42
JR
87}
88
89
26507458
JR
90if ( -f $statefile) {
91 open (IN, '<', $statefile) or die "Unable to open state-file: $!\n";
92 chomp($pos = <IN>);
93 chomp(my $stat_line = <IN>);
94 for my $stat (split /:/, $stat_line) {
95 my ($k, $v) = split /=/, $stat, 2;
96 $stats{$k} = $v;
97 }
98 while (<IN>) {
99 if (/^([0-9a-z.\-]+):(\d+)$/) {
100 $rejects{$1} = $2;
101 }
102 }
103 close IN;
a8801b42
JR
104}
105
26507458
JR
106if (!defined $pos) { # Initial run.
107 my $cursor = `journalctl --facility=mail --show-cursor -n 0 | tail -n 1`;
108 $pos = ($cursor =~ s/^-- cursor: //r);
a8801b42
JR
109}
110
26507458
JR
111$pos = parseLogfile($pos);
112
113if ( $ARGV[0] and $ARGV[0] eq "config" ) {
114 print "graph_title Postfix message throughput\n";
115 print "graph_args --base 1000 -l 0\n";
116 print "graph_vlabel mails / \${graph_period}\n";
117 print "graph_scale no\n";
26507458
JR
118 print "graph_category postfix\n";
119 print "graph_period minute\n";
120 print "messages.label messages\n";
121 print "messages.type DERIVE\n";
122 print "messages.min 0\n";
123
124 foreach my $key (sort keys %descriptions) {
125 print "$key.label $key\n";
126 print "$key.info $descriptions{$key}\n";
127 print "$key.type DERIVE\n";
128 print "$key.min 0\n";
129 }
130
131 foreach my $reason (sort keys %rejects) {
132 my $fieldname = clean_fieldname("r$reason");
133 print "$fieldname.label reject $reason\n";
134 print "$fieldname.type DERIVE\n";
135 print "$fieldname.min 0\n";
136 }
137 exit 0;
a8801b42
JR
138}
139
26507458
JR
140foreach my $type (sort keys %stats) {
141 print "$type.value " . $stats{$type} . "\n";
a8801b42 142}
26507458
JR
143foreach my $reason (sort keys %rejects) {
144 my $fieldname = clean_fieldname("r$reason");
145 print "$fieldname.value ", $rejects{$reason}, "\n";
a8801b42
JR
146}
147
148if(-l $statefile) {
26507458 149 die("$statefile is a symbolic link, refusing to touch it.");
a8801b42
JR
150}
151open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n";
26507458
JR
152print OUT "$pos\n";
153print OUT join(':', map { "$_=$stats{$_}" } keys %stats) . "\n";
154foreach my $i (sort keys %rejects) {
155 print OUT "$i:", $rejects{$i}, "\n";
a8801b42
JR
156}
157close OUT;
158
26507458
JR
159sub parseLogfile {
160 my ($start) = @_;
161 open(my $LOGFILE, '-|', "journalctl --facility=mail --after-cursor='$start' --show-cursor")
162 or die "Unable to open journalctl for reading: $? $!\n";
163
164 while (my $line = <$LOGFILE>) {
165 chomp $line;
166
167 if ($line =~ /qmgr\[[^]]+\]: [0-9A-Za-z]+: from=.*, size=\d+, nrcpt=(\d+)/) {
168 $stats{messages}++;
169 $stats{recipients} += $1;
170 } elsif ($line =~ /local\[[^]]+\]: [0-9A-Za-z]+: to=/) {
171 $stats{local}++;
172 } elsif ($line =~ /smtp\[[^]]+\]: [0-9A-Za-z]+: to=/) {
173 $stats{smtp}++;
174 } elsif ($line =~ /pickup\[[^]]+\]: [0-9A-Za-z]+: uid=/) {
175 $stats{pickup}++;
176 } elsif ($line =~ /smtpd\[[^]]+\]: [0-9A-Za-z]+: client=/) {
177 $stats{smtpd}++;
178 } elsif ($line =~ /bounce\[[^]]+\]: [0-9A-Za-z]+: /) {
179 $stats{bounce}++;
180 } elsif (
181 $line =~ /postfix\/smtpd.*proxy-reject: \S+ (\S+)/ ||
182 $line =~ /postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ ||
183 $line =~ /postfix\/cleanup.* reject: (\S+)/ ||
184 $line =~ /postfix\/cleanup.* milter-reject: \S+ \S+ \S+ (\S+)/
185 ) {
186 $rejects{$1}++;
187 } elsif ($line =~ /^-- cursor: (.+)$/) {
188 $start = $1;
189 }
190 }
191 close($LOGFILE);
192 return $start;
a8801b42
JR
193}
194
195# vim:syntax=perl