]> jfr.im git - munin-plugins.git/blame - postfix_mailstats
mysql: convert slow to /minute rate
[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";
118 print "graph_total Total\n";
119 print "graph_category postfix\n";
120 print "graph_period minute\n";
121 print "messages.label messages\n";
122 print "messages.type DERIVE\n";
123 print "messages.min 0\n";
124
125 foreach my $key (sort keys %descriptions) {
126 print "$key.label $key\n";
127 print "$key.info $descriptions{$key}\n";
128 print "$key.type DERIVE\n";
129 print "$key.min 0\n";
130 }
131
132 foreach my $reason (sort keys %rejects) {
133 my $fieldname = clean_fieldname("r$reason");
134 print "$fieldname.label reject $reason\n";
135 print "$fieldname.type DERIVE\n";
136 print "$fieldname.min 0\n";
137 }
138 exit 0;
a8801b42
JR
139}
140
26507458
JR
141foreach my $type (sort keys %stats) {
142 print "$type.value " . $stats{$type} . "\n";
a8801b42 143}
26507458
JR
144foreach my $reason (sort keys %rejects) {
145 my $fieldname = clean_fieldname("r$reason");
146 print "$fieldname.value ", $rejects{$reason}, "\n";
a8801b42
JR
147}
148
149if(-l $statefile) {
26507458 150 die("$statefile is a symbolic link, refusing to touch it.");
a8801b42
JR
151}
152open (OUT, '>', $statefile) or die "Unable to open statefile: $!\n";
26507458
JR
153print OUT "$pos\n";
154print OUT join(':', map { "$_=$stats{$_}" } keys %stats) . "\n";
155foreach my $i (sort keys %rejects) {
156 print OUT "$i:", $rejects{$i}, "\n";
a8801b42
JR
157}
158close OUT;
159
26507458
JR
160sub parseLogfile {
161 my ($start) = @_;
162 open(my $LOGFILE, '-|', "journalctl --facility=mail --after-cursor='$start' --show-cursor")
163 or die "Unable to open journalctl for reading: $? $!\n";
164
165 while (my $line = <$LOGFILE>) {
166 chomp $line;
167
168 if ($line =~ /qmgr\[[^]]+\]: [0-9A-Za-z]+: from=.*, size=\d+, nrcpt=(\d+)/) {
169 $stats{messages}++;
170 $stats{recipients} += $1;
171 } elsif ($line =~ /local\[[^]]+\]: [0-9A-Za-z]+: to=/) {
172 $stats{local}++;
173 } elsif ($line =~ /smtp\[[^]]+\]: [0-9A-Za-z]+: to=/) {
174 $stats{smtp}++;
175 } elsif ($line =~ /pickup\[[^]]+\]: [0-9A-Za-z]+: uid=/) {
176 $stats{pickup}++;
177 } elsif ($line =~ /smtpd\[[^]]+\]: [0-9A-Za-z]+: client=/) {
178 $stats{smtpd}++;
179 } elsif ($line =~ /bounce\[[^]]+\]: [0-9A-Za-z]+: /) {
180 $stats{bounce}++;
181 } elsif (
182 $line =~ /postfix\/smtpd.*proxy-reject: \S+ (\S+)/ ||
183 $line =~ /postfix\/smtpd.*reject: \S+ \S+ \S+ (\S+)/ ||
184 $line =~ /postfix\/cleanup.* reject: (\S+)/ ||
185 $line =~ /postfix\/cleanup.* milter-reject: \S+ \S+ \S+ (\S+)/
186 ) {
187 $rejects{$1}++;
188 } elsif ($line =~ /^-- cursor: (.+)$/) {
189 $start = $1;
190 }
191 }
192 close($LOGFILE);
193 return $start;
a8801b42
JR
194}
195
196# vim:syntax=perl