1 #!/usr/bin/perl
3 #
4 # collectd - contrib/exec-munin.px
5 # Copyright (C) 2007,2008 Florian Forster
6 #
7 # This program is free software; you can redistribute it and/or modify it
8 # under the terms of the GNU General Public License as published by the
9 # Free Software Foundation; only version 2 of the License is applicable.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with this program; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 #
20 # Authors:
21 # Florian octo Forster <octo at verplant.org>
22 #
24 use strict;
25 use warnings;
27 =head1 NAME
29 exec-munin.px
31 =head1 DESCRIPTION
33 This script allows you to use plugins that were written for Munin with
34 collectd's C<exec-plugin>. Since the data models of Munin and collectd are
35 quite different rewriting the plugins should be preferred over using this
36 transition layer. Having more than one "data source" for one "data set" doesn't
37 work with this script, for example.
39 =cut
41 use Sys::Hostname ('hostname');
42 use File::Basename ('basename');
43 use Config::General ('ParseConfig');
44 use Regexp::Common ('number');
46 our $ConfigFile = '/etc/exec-munin.conf';
47 our $TypeMap = {};
48 our $Scripts = [];
49 our $Interval = defined ($ENV{'COLLECTD_INTERVAL'}) ? (0 + $ENV{'COLLECTD_INTERVAL'}) : 300;
50 our $Hostname = defined ($ENV{'COLLECTD_HOSTNAME'}) ? $ENV{'COLLECTD_HOSTNAME'} : '';
52 main ();
53 exit (0);
55 # Configuration {{{
57 =head1 CONFIGURATION
59 This script reads it's configuration from F</etc/exec-munin.conf>. The
60 configuration is read using C<Config::General> which understands a Apache-like
61 config syntax, so it's very similar to the F<collectd.conf> syntax, too.
63 Here's a short sample config:
65 AddType voltage-in in
66 AddType voltage-out out
67 Interval 300
68 Script /usr/lib/munin/plugins/nut
70 The options have the following semantic (i.E<nbsp>e. meaning):
72 =over 4
74 =item B<AddType> I<to> I<from> [I<from> ...]
76 collectd uses B<types> to specify how data is structured. In Munin all data is
77 structured the same way, so some way of telling collectd how to handle the data
78 is needed. This option translates the so called "field names" of Munin to the
79 "types" of collectd. If more than one field are of the same type, e.E<nbsp>g.
80 the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
81 can use a hyphen to add a "type instance" to the type.
83 For a list of already defined "types" look at the F<types.db> file in
84 collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
86 =item B<Interval> I<Seconds>
88 Sets the interval in which the plugins are executed. This doesn't need to match
89 the interval setting of the collectd daemon. Usually, you want to execute the
90 Munin plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
91 seconds.
93 =item B<Script> I<File>
95 Adds a script to the list of scripts to be executed once per I<Interval>
96 seconds.
98 =back
100 =cut
102 sub handle_config_addtype
103 {
104 my $list = shift;
106 for (my $i = 0; $i < @$list; $i++)
107 {
108 my ($to, @from) = split (' ', $list->[$i]);
109 for (my $j = 0; $j < @from; $j++)
110 {
111 $TypeMap->{$from[$j]} = $to;
112 }
113 }
114 } # handle_config_addtype
116 sub handle_config_script
117 {
118 my $scripts = shift;
120 for (my $i = 0; $i < @$scripts; $i++)
121 {
122 my $script = $scripts->[$i];
123 if (!-e $script)
124 {
125 print STDERR "Script `$script' doesn't exist.\n";
126 }
127 elsif (!-x $script)
128 {
129 print STDERR "Script `$script' exists but is not executable.\n";
130 }
131 else
132 {
133 push (@$Scripts, $script);
134 }
135 } # for $i
136 } # handle_config_script
138 sub handle_config
139 {
140 my $config = shift;
142 if (defined ($config->{'addtype'}))
143 {
144 if (ref ($config->{'addtype'}) eq 'ARRAY')
145 {
146 handle_config_addtype ($config->{'addtype'});
147 }
148 elsif (ref ($config->{'addtype'}) eq '')
149 {
150 handle_config_addtype ([$config->{'addtype'}]);
151 }
152 else
153 {
154 print STDERR "Cannot handle ref type '"
155 . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
156 }
157 }
159 if (defined ($config->{'script'}))
160 {
161 if (ref ($config->{'script'}) eq 'ARRAY')
162 {
163 handle_config_script ($config->{'script'});
164 }
165 elsif (ref ($config->{'script'}) eq '')
166 {
167 handle_config_script ([$config->{'script'}]);
168 }
169 else
170 {
171 print STDERR "Cannot handle ref type '"
172 . ref ($config->{'script'}) . "' for option 'Script'.\n";
173 }
174 }
176 if (defined ($config->{'interval'})
177 && (ref ($config->{'interval'}) eq ''))
178 {
179 my $num = int ($config->{'interval'});
180 if ($num > 0)
181 {
182 $Interval = $num;
183 }
184 }
185 } # handle_config }}}
187 sub execute_script
188 {
189 my $fh;
190 my $pinst;
191 my $time = time ();
192 my $script = shift;
193 my $host = $Hostname || hostname () || 'localhost';
194 if (!open ($fh, '-|', $script))
195 {
196 print STDERR "Cannot execute $script: $!";
197 return;
198 }
200 $pinst = basename ($script);
202 while (my $line = <$fh>)
203 {
204 chomp ($line);
205 if ($line =~ m#^([^\.\-/]+)\.value\s+($RE{num}{real})#)
206 {
207 my $field = $1;
208 my $value = $2;
209 my $type = (defined ($TypeMap->{$field})) ? $TypeMap->{$field} : $field;
210 my $ident = "$host/munin-$pinst/$type";
212 $ident =~ s/"/\\"/g;
214 print qq(PUTVAL "$ident" interval=$Interval $time:$value\n);
215 }
216 }
218 close ($fh);
219 } # execute_script
221 sub main
222 {
223 my $last_run;
224 my $next_run;
226 my %config = ParseConfig (-ConfigFile => $ConfigFile,
227 -AutoTrue => 1,
228 -LowerCaseNames => 1);
229 handle_config (\%config);
231 while (42)
232 {
233 $last_run = time ();
234 $next_run = $last_run + $Interval;
236 for (@$Scripts)
237 {
238 execute_script ($_);
239 }
241 while ((my $timeleft = ($next_run - time ())) > 0)
242 {
243 sleep ($timeleft);
244 }
245 }
246 } # main
248 =head1 REQUIREMENTS
250 This script requires the following Perl modules to be installed:
252 =over 4
254 =item C<Config::General>
256 =item C<Regexp::Common>
258 =back
260 =head1 SEE ALSO
262 L<http://munin.projects.linpro.no/>,
263 L<http://collectd.org/>,
264 L<collectd-exec(5)>
266 =head1 AUTHOR
268 Florian octo Forster E<lt>octo at verplant.orgE<gt>
270 =cut
272 # vim: set sw=2 sts=2 ts=8 fdm=marker :