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 = 300;
51 main ();
52 exit (0);
54 # Configuration {{{
56 =head1 CONFIGURATION
58 This script reads it's configuration from F</etc/exec-munin.conf>. The
59 configuration is read using C<Config::General> which understands a Apache-like
60 config syntax, so it's very similar to the F<collectd.conf> syntax, too.
62 Here's a short sample config:
64 AddType voltage-in in
65 AddType voltage-out out
66 Interval 300
67 Script /usr/lib/munin/plugins/nut
69 The options have the following semantic (i.E<nbsp>e. meaning):
71 =over 4
73 =item B<AddType> I<to> I<from> [I<from> ...]
75 collectd uses B<types> to specify how data is structured. In Munin all data is
76 structured the same way, so some way of telling collectd how to handle the data
77 is needed. This option translates the so called "field names" of Munin to the
78 "types" of collectd. If more than one field are of the same type, e.E<nbsp>g.
79 the C<nut> plugin above provides C<in> and C<out> which are both voltages, you
80 can use a hyphen to add a "type instance" to the type.
82 For a list of already defined "types" look at the F<types.db> file in
83 collectd's shared data directory, e.E<nbsp>g. F</usr/share/collectd/>.
85 =item B<Interval> I<Seconds>
87 Sets the interval in which the plugins are executed. This doesn't need to match
88 the interval setting of the collectd daemon. Usually, you want to execute the
89 Munin plugins much less often, e.E<nbsp>g. every 300 seconds versus every 10
90 seconds.
92 =item B<Script> I<File>
94 Adds a script to the list of scripts to be executed once per I<Interval>
95 seconds.
97 =back
99 =cut
101 sub handle_config_addtype
102 {
103 my $list = shift;
105 for (my $i = 0; $i < @$list; $i++)
106 {
107 my ($to, @from) = split (' ', $list->[$i]);
108 for (my $j = 0; $j < @from; $j++)
109 {
110 $TypeMap->{$from[$j]} = $to;
111 }
112 }
113 } # handle_config_addtype
115 sub handle_config_script
116 {
117 my $scripts = shift;
119 for (my $i = 0; $i < @$scripts; $i++)
120 {
121 my $script = $scripts->[$i];
122 if (!-e $script)
123 {
124 print STDERR "Script `$script' doesn't exist.\n";
125 }
126 elsif (!-x $script)
127 {
128 print STDERR "Script `$script' exists but is not executable.\n";
129 }
130 else
131 {
132 push (@$Scripts, $script);
133 }
134 } # for $i
135 } # handle_config_script
137 sub handle_config
138 {
139 my $config = shift;
141 if (defined ($config->{'addtype'}))
142 {
143 if (ref ($config->{'addtype'}) eq 'ARRAY')
144 {
145 handle_config_addtype ($config->{'addtype'});
146 }
147 elsif (ref ($config->{'addtype'}) eq '')
148 {
149 handle_config_addtype ([$config->{'addtype'}]);
150 }
151 else
152 {
153 print STDERR "Cannot handle ref type '"
154 . ref ($config->{'addtype'}) . "' for option 'AddType'.\n";
155 }
156 }
158 if (defined ($config->{'script'}))
159 {
160 if (ref ($config->{'script'}) eq 'ARRAY')
161 {
162 handle_config_script ($config->{'script'});
163 }
164 elsif (ref ($config->{'script'}) eq '')
165 {
166 handle_config_script ([$config->{'script'}]);
167 }
168 else
169 {
170 print STDERR "Cannot handle ref type '"
171 . ref ($config->{'script'}) . "' for option 'Script'.\n";
172 }
173 }
175 if (defined ($config->{'interval'})
176 && (ref ($config->{'interval'}) eq ''))
177 {
178 my $num = int ($config->{'interval'});
179 if ($num > 0)
180 {
181 $Interval = $num;
182 }
183 }
184 } # handle_config }}}
186 sub execute_script
187 {
188 my $fh;
189 my $pinst;
190 my $time = time ();
191 my $script = shift;
192 my $host = hostname () || 'localhost';
193 if (!open ($fh, '-|', $script))
194 {
195 print STDERR "Cannot execute $script: $!";
196 return;
197 }
199 $pinst = basename ($script);
201 while (my $line = <$fh>)
202 {
203 chomp ($line);
204 if ($line =~ m#^([^\.\-/]+)\.value\s+($RE{num}{real})#)
205 {
206 my $field = $1;
207 my $value = $2;
208 my $type = (defined ($TypeMap->{$field})) ? $TypeMap->{$field} : $field;
210 print "$host/munin-$pinst/$type interval=$Interval $time:$value\n";
211 }
212 }
214 close ($fh);
215 } # execute_script
217 sub main
218 {
219 my $last_run;
220 my $next_run;
222 my %config = ParseConfig (-ConfigFile => $ConfigFile,
223 -AutoTrue => 1,
224 -LowerCaseNames => 1);
225 handle_config (\%config);
227 while (42)
228 {
229 $last_run = time ();
230 $next_run = $last_run + $Interval;
232 for (@$Scripts)
233 {
234 execute_script ($_);
235 }
237 while ((my $timeleft = ($next_run - time ())) > 0)
238 {
239 sleep ($timeleft);
240 }
241 }
242 } # main
244 =head1 REQUIREMENTS
246 This script requires the following Perl modules to be installed:
248 =over 4
250 =item C<Config::General>
252 =item C<Regexp::Common>
254 =back
256 =head1 SEE ALSO
258 L<http://munin.projects.linpro.no/>,
259 L<http://collectd.org/>,
260 L<collectd-exec(5)>
262 =head1 AUTHOR
264 Florian octo Forster E<lt>octo at verplant.orgE<gt>
266 =cut
268 # vim: set sw=2 sts=2 ts=8 fdm=marker :