]> jfr.im git - munin-plugins.git/blob - postfix_mailvolume
support for a 2nd unbound plugin
[munin-plugins.git] / postfix_mailvolume
1 #!/usr/bin/perl -w
2 # -*- perl -*-
3
4 =head1 NAME
5
6 postfix_mailvolume - Plugin to monitor the volume of mails delivered
7 by postfix.
8
9 =head1 APPLICABLE SYSTEMS
10
11 Any postfix.
12
13 =head1 CONFIGURATION
14
15 The following shows the default configuration.
16
17 [postfix*]
18 env.logdir /var/log
19 env.logfile syslog
20
21 =head1 INTERPRETATION
22
23 The plugin shows the number of bytes of mail that has passed through
24 the postfix installation.
25
26 =head1 MAGIC MARKERS
27
28 #%# family=auto
29 #%# capabilities=autoconf
30
31 =head1 BUGS
32
33 None known
34
35 =head1 VERSION
36
37 v1.1 2018-03-24
38 * calculate extra field for mail volume that is actually delivered ("volume_delivered")
39
40 =head1 AUTHOR
41
42 Copyright (C) 2002-2008.
43
44 No author is documented.
45
46 =head1 LICENSE
47
48 GPLv2
49
50 =cut
51
52 use strict;
53 use warnings;
54 use Munin::Plugin;
55
56 my $pos = undef;
57 # the volume that was actually delivered
58 my $volume_delivered = 0;
59 my %volumes_per_queue_id = ();
60 my $serialized_volumes_queue;
61 my %expired_queue_ids = ();
62 # Discard old queue IDs after a while (otherwise the state storage grows infinitely). We need to
63 # store the IDs long enough for the gap between two delivery attempts. Thus multiple hours are
64 # recommended.
65 use constant queue_id_expiry => 6 * 3600;
66
67 sub parseLogfile {
68 my ($start) = @_;
69
70 open(my $LOGFILE, '-|', "journalctl --facility=mail --after-cursor='$start' --show-cursor");
71
72 while (my $line = <$LOGFILE>) {
73 chomp $line;
74
75 if ($line =~ /qmgr.*: ([0-9A-Za-z]+): from=.*, size=([0-9]+)/) {
76 # The line with queue ID and size may pass along multiple times (every time the mail
77 # is moved into the active queue for another delivery attempt). The size should always
78 # be the same.
79 if (not exists($volumes_per_queue_id{$1})) {
80 $volumes_per_queue_id{$1} = {timestamp => time};
81 }
82 # probably it is the same value as before
83 $volumes_per_queue_id{$1}->{size} = $2;
84 } elsif ($line =~ / ([0-9A-Za-z]+): to=.*, status=sent /) {
85 # The "sent" line is repeated for every successful delivery for each recipient.
86 if (exists($volumes_per_queue_id{$1})) {
87 $volume_delivered += $volumes_per_queue_id{$1}->{size};
88 $volumes_per_queue_id{$1}->{timestamp} = time;
89 }
90 } elsif ($line =~ /^-- cursor: (.+)$/) {
91 $start = $1;
92 }
93 }
94 close($LOGFILE);
95 # remove all expired queue IDs
96 my @expired_queue_ids;
97 for my $key (keys %volumes_per_queue_id) {
98 if (time > $volumes_per_queue_id{$key}->{timestamp} + queue_id_expiry) {
99 push @expired_queue_ids, $key;
100 }
101 }
102 delete(@volumes_per_queue_id{@expired_queue_ids});
103 return $start;
104 }
105
106 if ( $ARGV[0] and $ARGV[0] eq "autoconf" ) {
107 my $logfile;
108 `which postconf >/dev/null 2>/dev/null`;
109 if (!$?) { # if postconf returns success
110 `journalctl --facility=mail --show-cursor -n 0`;
111 if (!$?) { # if journalctl returns success
112 print "yes\n";
113 } else {
114 print "no (journalctl returned error)\n";
115 }
116 } else {
117 print "no (postfix not found)\n";
118 }
119
120 exit 0;
121 }
122
123
124 if ( $ARGV[0] and $ARGV[0] eq "config" ) {
125 print "graph_title Postfix bytes throughput\n";
126 print "graph_args --base 1000 -l 0\n";
127 print "graph_scale yes\n";
128 print "graph_category postfix\n";
129 print "graph_vlabel bytes / \${graph_period}\n";
130 print "graph_period minute\n";
131 print "volume.label delivered volume\n";
132 print "volume.type DERIVE\n";
133 print "volume.min 0\n";
134 exit 0;
135 }
136
137
138 # load the stored data
139 ($pos, $volume_delivered, $serialized_volumes_queue) = restore_state();
140
141
142 if (!defined($volume_delivered) || !defined($pos) || !$pos) { # they could be defined but 0 if the old plugin was run
143
144 # No state file present. Avoid startup spike: Do not read log
145 # file up to now, but remember how large it is now, and next
146 # time read from there.
147
148 my $cursor = `journalctl --facility=mail --show-cursor -n 0 | tail -n 1`;
149 $pos = ($cursor =~ s/^-- cursor: //r);
150
151 $volume_delivered = 0;
152 %volumes_per_queue_id = ();
153 } else {
154 # decode the serialized hash
155 # source format: "$id1=$size1:$timestamp1 $id2=$size2:$timestamp2 ..."
156 # The "serialized" value may be undefined, in case we just upgraded from the version before
157 # 2018, since that old version stored only two fields in the state file. Tolerate this.
158 for my $queue_item_descriptor (split(/ /, $serialized_volumes_queue || "")) {
159 (my $queue_item_id, my $queue_item_content) = split(/=/, $queue_item_descriptor);
160 (my $size, my $timestamp) = split(/:/, $queue_item_content);
161 $volumes_per_queue_id{$queue_item_id} = { size => int($size), timestamp => int($timestamp) };
162 }
163 $pos = parseLogfile($pos);
164 }
165
166 print "volume.value $volume_delivered\n";
167
168 # serialize the hash to a string (see "source format" above)
169 $serialized_volumes_queue = join(" ", map { sprintf("%s=%s", $_, sprintf("%d:%d", $volumes_per_queue_id{$_}->{size}, $volumes_per_queue_id{$_}->{timestamp})) } keys %volumes_per_queue_id);
170 save_state($pos, $volume_delivered, $serialized_volumes_queue);
171
172 # vim:syntax=perl