]> jfr.im git - munin-plugins.git/blob - postfix_mailstats
support for a 2nd unbound plugin
[munin-plugins.git] / postfix_mailstats
1 #!/usr/bin/perl -w
2 # -*- perl -*-
3
4 =head1 NAME
5
6 postfix_mailstats - Plugin to monitor the number of mails delivered and
7 rejected by postfix
8
9 =head1 CONFIGURATION
10
11 Configuration parameters for /etc/munin/postfix_mailstats,
12 if 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
26 Records show that the plugin was contributed by Nicolai Langfeldt in
27 2003. Nicolai can't find anything in his email about this and expects
28 the plugin is based on the corresponding exim plugin - to which it now
29 bears no resemblence.
30
31 =head1 LICENSE
32
33 GPLv2
34
35 =head1 MAGIC MARKERS
36
37 =begin comment
38
39 These magic markers are used by munin-node-configure when installing
40 munin-node.
41
42 =end comment
43
44 #%# family=manual
45 #%# capabilities=autoconf
46
47 =head1 RANDOM COMMENTS
48
49 Would be cool if someone ported this to Munin::Plugin for both state
50 file and log tailing.
51
52 =cut
53
54 use strict;
55
56 use Munin::Plugin;
57
58 my $statefile = $ENV{'MUNIN_PLUGSTATE'} . "/munin-plugin-postfix_mailstats.state";
59 my $pos;
60 my %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 );
69 my %stats = map { $_ => 0 } keys %descriptions;
70 my %rejects = ();
71
72 if ( $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;
87 }
88
89
90 if ( -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;
104 }
105
106 if (!defined $pos) { # Initial run.
107 my $cursor = `journalctl --facility=mail --show-cursor -n 0 | tail -n 1`;
108 $pos = ($cursor =~ s/^-- cursor: //r);
109 }
110
111 $pos = parseLogfile($pos);
112
113 if ( $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";
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;
138 }
139
140 foreach my $type (sort keys %stats) {
141 print "$type.value " . $stats{$type} . "\n";
142 }
143 foreach my $reason (sort keys %rejects) {
144 my $fieldname = clean_fieldname("r$reason");
145 print "$fieldname.value ", $rejects{$reason}, "\n";
146 }
147
148 if(-l $statefile) {
149 die("$statefile is a symbolic link, refusing to touch it.");
150 }
151 open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n";
152 print OUT "$pos\n";
153 print OUT join(':', map { "$_=$stats{$_}" } keys %stats) . "\n";
154 foreach my $i (sort keys %rejects) {
155 print OUT "$i:", $rejects{$i}, "\n";
156 }
157 close OUT;
158
159 sub 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;
193 }
194
195 # vim:syntax=perl