58d1e68632b9e288cfe349c10bea5ac30756322a
1 #!/usr/bin/perl
3 =head1 NAME
5 Collectd - plugin for filling collectd with stats
7 =head1 INSTALLATION
9 Just copy Collectd.pm into your SpamAssassin Plugin path
10 (e.g /usr/share/perl5/Mail/SpamAssassin/Plugin/) and
11 add a loadplugin call into your init.pre file.
13 =head1 SYNOPSIS
15 loadplugin Mail::SpamAssassin::Plugin::Collectd
17 =head1 USER SETTINGS
19 =over 4
21 =item collectd_socket [ socket path ] (default: /var/run/collectd-email)
23 Where the collectd socket is
25 =cut
27 =item collectd_buffersize [ size ] (default: 256)
29 the email plugin uses a fixed buffer, if a line exceeds this size
30 it has to be continued in another line. (This is of course handled internally)
31 If you have changed this setting please get it in sync with the SA Plugin
32 config.
34 =cut
36 =item collectd_timeout [ sec ] (default: 2)
38 if sending data to to collectd takes too long the connection will be aborted.
40 =cut
42 =item collectd_retries [ tries ] (default: 3)
44 the collectd plugin uses a tread pool, if this is empty the connection fails,
45 the SA Plugin then tries to reconnect. With this variable you can indicate how
46 often it should try.
48 =cut
50 =head1 DESCRIPTION
52 This modules uses the email plugin of collectd from Sebastian Harl to
53 collect statistical informations in rrd files to create some nice looking
54 graphs with rrdtool. They communicate over a unix socket that the collectd
55 plugin creates. The generated graphs will be placed in /var/lib/collectd/email
57 =head1 AUTHOR
59 Alexander Wirt <formorer@formorer.de>
61 =head1 COPYRIGHT
63 Copyright 2006 Alexander Wirt <formorer@formorer.de>
65 This program is free software; you can redistribute it and/or modify
66 it under the the terms of either:
68 a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
70 or
72 b) the GPL (http://www.gnu.org/copyleft/gpl.html)
74 use whatever you like more.
76 =cut
78 package Mail::SpamAssassin::Plugin::Collectd;
80 use Mail::SpamAssassin::Plugin;
81 use Mail::SpamAssassin::Logger;
82 use strict;
83 use bytes;
84 use warnings;
85 use Time::HiRes qw(usleep);
86 use IO::Socket;
88 use vars qw(@ISA);
89 @ISA = qw(Mail::SpamAssassin::Plugin);
91 sub new {
92 my ($class, $mailsa) = @_;
94 # the usual perlobj boilerplate to create a subclass object
95 $class = ref($class) || $class;
96 my $self = $class->SUPER::new($mailsa);
97 bless ($self, $class);
99 # register our config options
100 $self->set_config($mailsa->{conf});
102 # and return the new plugin object
103 return $self;
104 }
106 sub set_config {
107 my ($self, $conf) = @_;
108 my @cmds = ();
110 push (@cmds, {
111 setting => 'collectd_buffersize',
112 default => 256,
113 type =>
114 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
115 });
117 push (@cmds, {
118 setting => 'collectd_socket',
119 default => '/var/run/collectd-email',
120 type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
121 });
123 push (@cmds, {
124 setting => 'collectd_timeout',
125 default => 2,
126 type =>
127 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
128 });
130 push (@cmds, {
131 setting => 'collectd_retries',
132 default => 3,
133 type =>
134 $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
135 });
138 $conf->{parser}->register_commands(\@cmds);
139 }
141 sub check_end {
142 my ($self, $params) = @_;
143 my $message_status = $params->{permsgstatus};
144 #create new connection to our socket
145 eval {
146 local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required
148 #generate a timeout
149 alarm $self->{main}->{conf}->{collectd_timeout};
151 my $sock;
152 #try at least $self->{main}->{conf}->{collectd_retries} to get a
153 #connection
154 for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) {
155 my ($socket_path) = $self->{main}->{conf}->{collectd_socket} =~ /(.*)/ # Untaint path, which can contain any characters.
156 last if $sock = new IO::Socket::UNIX $socket_path;
157 #sleep a random value between 0 and 50 microsecs to try for a new
158 #thread
159 usleep(int(rand(50)));
160 }
162 die("could not connect to " .
163 $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock;
165 $sock->autoflush(1);
167 my $score = $message_status->{score};
168 #get the size of the message
169 my $body = $message_status->{msg}->{pristine_body};
171 my $len = length($body);
173 if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) {
174 #hey we have spam
175 print $sock "e:spam:$len\n";
176 } else {
177 print $sock "e:ham:$len\n";
178 }
179 print $sock "s:$score\n";
180 my @tmp_array;
181 my @tests = @{$message_status->{test_names_hit}};
183 my $buffersize = $self->{main}->{conf}->{collectd_buffersize};
184 dbg("collectd: buffersize: $buffersize");
186 while (scalar(@tests) > 0) {
187 push (@tmp_array, pop(@tests));
188 if (length(join(',', @tmp_array) . '\n') > $buffersize) {
189 push (@tests, pop(@tmp_array));
190 if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) {
191 dbg("collectd: this shouldn't happen. Do you have tests"
192 ." with names that have more than ~ $buffersize Bytes?");
193 return 1;
194 } else {
195 dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" );
196 print $sock "c:" . join(',', @tmp_array) . "\n";
197 #clean the array
198 @tmp_array = ();
199 }
200 } elsif ( scalar(@tests) == 0 ) {
201 dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' );
202 print $sock "c:" . join(',', @tmp_array) . "\n";
203 }
204 }
205 close($sock);
206 alarm 0;
207 };
208 if ($@) {
209 my $message = $@;
210 chomp($message);
211 info("collectd: $message");
212 return -1;
213 }
214 }
216 1;
218 # vim: syntax=perl sw=4 ts=4 noet shiftround