SA plugin: Added reconnect support (collectd_retries), code cleanup
[collectd.git] / contrib / SpamAssassin / Collectd.pm
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 Collectd - plugin for filling collectd with stats 
6
7 =head1 INSTALLATION
8
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. 
12
13 =head1 SYNOPSIS
14
15   loadplugin    Mail::SpamAssassin::Plugin::Collectd
16
17 =head1 USER SETTINGS
18
19 =over 4
20
21 =item collectd_socket [ socket path ]       (default: /tmp/.collectd-email)
22
23 Where the collectd socket is
24
25 =cut 
26
27 =item collectd_buffersize [ size ] (default: 256) 
28
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. 
33
34 =cut 
35 =head1 DESCRIPTION
36
37 This modules uses the email plugin of collectd from Sebastian Harl to
38 collect statistical informations in rrd files to create some nice looking
39 graphs with rrdtool. They communicate over a unix socket that the collectd
40 plugin creates. The generated graphs will be placed in /var/lib/collectd/email
41
42 =head1 AUTHOR
43
44 Alexander Wirt <formorer@formorer.de>
45
46 =head1 COPYRIGHT
47
48  Copyright 2006 Alexander Wirt <formorer@formorer.de> 
49  
50  This program is free software; you can redistribute it and/or modify 
51  it under the the terms of either: 
52
53  a) the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
54
55  or
56
57  b) the GPL (http://www.gnu.org/copyleft/gpl.html)  
58
59  use whatever you like more. 
60
61 =cut
62
63 package Mail::SpamAssassin::Plugin::Collectd;
64
65 use Mail::SpamAssassin::Plugin;
66 use Mail::SpamAssassin::Logger;
67 use strict;
68 use bytes; 
69 use warnings;
70 use Time::HiRes qw(usleep);
71 use IO::Socket;
72
73 use vars qw(@ISA);
74 @ISA = qw(Mail::SpamAssassin::Plugin);
75
76 sub new {
77     my ($class, $mailsa) = @_;
78
79     # the usual perlobj boilerplate to create a subclass object
80     $class = ref($class) || $class;
81     my $self = $class->SUPER::new($mailsa);
82     bless ($self, $class);
83
84     # register our config options
85     $self->set_config($mailsa->{conf});
86
87     # and return the new plugin object
88     return $self;
89 }
90
91 sub set_config {
92     my ($self, $conf) = @_;
93     my @cmds = ();
94
95     push (@cmds, {
96             setting => 'collectd_buffersize',
97             default => 256,
98             type =>
99             $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
100         });
101
102     push (@cmds, {
103             setting => 'collectd_socket', 
104             default => '/tmp/.collectd-email',
105             type => $Mail::SpamAssassin::Conf::CONF_TYPE_STRING,
106     });
107
108         push (@cmds, {
109                         setting => 'collectd_timeout',
110                         default => 2,
111                         type =>
112                         $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
113         });
114
115         push (@cmds, {
116                         setting => 'collectd_retries',
117                         default => 3,
118                         type =>
119                         $Mail::SpamAssassin::Conf::CONF_TYPE_NUMERIC,
120         });
121
122
123     $conf->{parser}->register_commands(\@cmds);
124 }
125
126 sub check_end {
127     my ($self, $params) = @_;
128     my $message_status = $params->{permsgstatus};
129         #create  new connection to our socket
130         eval {
131                 local $SIG{ALRM} = sub { die "Sending to collectd timed out.\n" }; # NB: \n required
132
133                 #generate a timeout
134                 alarm $self->{main}->{conf}->{collectd_timeout};
135
136                 my $sock;
137                 #try at least $self->{main}->{conf}->{collectd_retries} to get a
138                 #connection
139                 for (my $i = 0; $i < $self->{main}->{conf}->{collectd_retries} ; ++$i) {
140                         last if $sock = new IO::Socket::UNIX
141                                 ($self->{main}->{conf}->{collectd_socket});
142                         #sleep a random value between 0 and 50 microsecs to try for a new
143                         #thread
144                         usleep(int(rand(50))); 
145                 }
146
147                 die("could not connect to " .
148                                 $self->{main}->{conf}->{collectd_socket} . ": $! - collectd plugin disabled") unless $sock; 
149
150                 $sock->autoflush(1);
151
152                 my $score = $message_status->{score};
153                 #get the size of the message 
154                 my $body = $message_status->{msg}->{pristine_body};
155
156                 my $len = length($body);
157
158                 if ($message_status->{score} >= $self->{main}->{conf}->{required_score} ) {
159                         #hey we have spam
160                         print $sock "e:spam:$len\n";
161                 } else {
162                         print $sock "e:ham:$len\n";
163                 }
164                 print $sock "s:$score\n";
165                 my @tmp_array; 
166                 my @tests = @{$message_status->{test_names_hit}};
167
168                 my $buffersize = $self->{main}->{conf}->{collectd_buffersize}; 
169                 dbg("collectd: buffersize: $buffersize"); 
170
171                 while  (scalar(@tests) > 0) {
172                 push (@tmp_array, pop(@tests)); 
173                         if (length(join(',', @tmp_array) . '\n') > $buffersize) {
174                                 push (@tests, pop(@tmp_array)); 
175                                         if (length(join(',', @tmp_array) . '\n') > $buffersize or scalar(@tmp_array) == 0) {
176                                                 dbg("collectd: this shouldn't happen. Do you have tests"
177                                                         ." with names that have more than ~ $buffersize Bytes?");
178                                                 return 1; 
179                                         } else {
180                                                 dbg ( "collectd: c:" . join(',', @tmp_array) . "\n" ); 
181                                                 print $sock "c:" . join(',', @tmp_array) . "\n"; 
182                                                 #clean the array
183                                                 @tmp_array = ();
184                                         } 
185                         } elsif ( scalar(@tests) == 0 ) {
186                                 dbg ( "collectd: c:" . join(',', @tmp_array) . '\n' );
187                                 print $sock "c:" . join(',', @tmp_array) . "\n";
188                         }
189                 }
190                 close($sock); 
191                 alarm 0; 
192         };
193         if ($@) {
194                 my $message = $@; 
195                 chomp($message); 
196                 info("collectd: $message");
197                 return -1; 
198         }
199 }
200
201 1;
202
203 # vim: syntax=perl sw=4 ts=4 noet shiftround