60097e5c43653c8e2cd3aedabceb8bc877236dfe
1 package Collectd::Graph::Type;
3 =head1 NAME
5 Collectd::Graph::Type - Base class for the collectd graphing infrastructure
7 =cut
9 # Copyright (C) 2008 Florian octo Forster <octo at verplant.org>
10 #
11 # This program is free software; you can redistribute it and/or modify it under
12 # the terms of the GNU General Public License as published by the Free Software
13 # Foundation; only version 2 of the License is applicable.
14 #
15 # This program is distributed in the hope that it will be useful, but WITHOUT
16 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
18 # details.
19 #
20 # You should have received a copy of the GNU General Public License along with
21 # this program; if not, write to the Free Software Foundation, Inc.,
22 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 use strict;
25 use warnings;
27 use Carp (qw(confess cluck));
28 use RRDs ();
29 use URI::Escape (qw(uri_escape));
31 use Collectd::Graph::Common (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue
32 ident_to_filename
33 ident_to_string
34 get_faded_color));
36 return (1);
38 =head1 DESCRIPTION
40 This module serves as base class for more specialized classes realizing
41 specific "types".
43 =head1 MEMBER VARIABLES
45 As typical in Perl, a Collectd::Graph::Type object is a blessed hash reference.
46 Member variables are entries in that hash. Inheriting classes are free to add
47 additional entries. To set the member variable B<foo> to B<42>, do:
49 $obj->{'foo'} = 42;
51 The following members control the behavior of Collectd::Graph::Type.
53 =over 4
55 =item B<files> (array reference)
57 List of RRD files. Each file is passed as "ident", i.E<nbsp>e. broken up into
58 "hostname", "plugin", "type" and optionally "plugin_instance" and
59 "type_instance". Use the B<addFiles> method rather than setting this directly.
61 =item B<data_sources> (array reference)
63 List of data sources in the RRD files. If this is not given, the default
64 implementation of B<getDataSources> will use B<RRDs::info> to find out which
65 data sources are contained in the files.
67 =item B<ds_names> (array reference)
69 Names of the data sources as printed in the graph. Should be in the same order
70 as the data sources are returned by B<getDataSources>.
72 =item B<rrd_title> (string)
74 Title of the RRD graph. The title can contain "{hostname}", "{plugin}" and so
75 on which are replaced with their actual value. See the B<getTitle> method
76 below.
78 =item B<rrd_opts> (array reference)
80 List of options directly passed to B<RRDs::graph>.
82 =item B<rrd_format> (string)
84 Format to use with B<GPRINT>. Defaults to C<%5.1lf>.
86 =item B<rrd_colors> (hash reference)
88 Mapping of data source names to colors, used when graphing the different data
89 sources. Colors are given in the typical hexadecimal RGB form, but without
90 leading "#", e.E<nbsp>g.:
92 $obj->{'rrd_colors'} = {foo => 'ff0000', bar => '00ff00'};
94 =back
96 =head1 METHODS
98 The following methods are used by the graphing front end and may be overwritten
99 to customize their behavior.
101 =over 4
103 =cut
105 sub _get_ds_from_file
106 {
107 my $file = shift;
108 my $info = RRDs::info ($file);
109 my %ds = ();
110 my @ds = ();
112 if (!$info || (ref ($info) ne 'HASH'))
113 {
114 return;
115 }
117 for (keys %$info)
118 {
119 if (m/^ds\[([^\]]+)\]/)
120 {
121 $ds{$1} = 1;
122 }
123 }
125 @ds = (keys %ds);
126 if (wantarray ())
127 {
128 return (@ds);
129 }
130 elsif (@ds)
131 {
132 return (\@ds);
133 }
134 else
135 {
136 return;
137 }
138 } # _get_ds_from_file
140 sub new
141 {
142 my $pkg = shift;
143 my $obj = bless ({files => []}, $pkg);
145 if (@_)
146 {
147 $obj->addFiles (@_);
148 }
150 return ($obj);
151 }
153 =item B<addFiles> ({ I<ident> }, [...])
155 Adds the given idents (which are hash references) to the B<files> member
156 variable, see above.
158 =cut
160 sub addFiles
161 {
162 my $obj = shift;
163 push (@{$obj->{'files'}}, @_);
164 }
166 =item B<getGraphsNum> ()
168 Returns the number of graphs that can be generated from the added files. By
169 default this number equals the number of files.
171 =cut
173 sub getGraphsNum
174 {
175 my $obj = shift;
176 return (scalar @{$obj->{'files'}});
177 }
179 =item B<getDataSources> ()
181 Returns the names of the data sources. If the B<data_sources> member variable
182 is unset B<RRDs::info> is used to read that information from the first file.
183 Set the B<data_sources> member variable instead of overloading this method!
185 =cut
187 sub getDataSources
188 {
189 my $obj = shift;
191 if (!defined $obj->{'data_sources'})
192 {
193 my $ident;
194 my $filename;
196 if (!@{$obj->{'files'}})
197 {
198 return;
199 }
201 $ident = $obj->{'files'}[0];
202 $filename = ident_to_filename ($ident);
204 $obj->{'data_sources'} = _get_ds_from_file ($filename);
205 if (!$obj->{'data_sources'})
206 {
207 cluck ("_get_ds_from_file ($filename) failed.");
208 }
209 }
211 if (!defined $obj->{'data_sources'})
212 {
213 return;
214 }
215 elsif (wantarray ())
216 {
217 return (@{$obj->{'data_sources'}})
218 }
219 else
220 {
221 $obj->{'data_sources'};
222 }
223 } # getDataSources
226 =item B<getTitle> (I<$index>)
228 Returns the title of the I<$index>th B<graph> (not necessarily file!). If the
229 B<rrd_title> member variable is unset, a generic title is generated from the
230 ident. Otherwise the substrings "{hostname}", "{plugin}", "{plugin_instance}",
231 "{type}", and "{type_instance}" are replaced by their respective values.
233 =cut
235 sub getTitle
236 {
237 my $obj = shift;
238 my $ident = shift;
239 my $title = $obj->{'rrd_title'};
241 if (!$title)
242 {
243 return (ident_to_string ($ident));
244 }
246 my $hostname = $ident->{'hostname'};
247 my $plugin = $ident->{'plugin'};
248 my $plugin_instance = $ident->{'plugin_instance'};
249 my $type = $ident->{'type'};
250 my $type_instance = $ident->{'type_instance'};
252 if (!defined $plugin_instance)
253 {
254 $plugin_instance = 'no instance';
255 }
257 if (!defined $type_instance)
258 {
259 $type_instance = 'no instance';
260 }
262 $title =~ s#{hostname}#$hostname#g;
263 $title =~ s#{plugin}#$plugin#g;
264 $title =~ s#{plugin_instance}#$plugin_instance#g;
265 $title =~ s#{type}#$type#g;
266 $title =~ s#{type_instance}#$type_instance#g;
268 return ($title);
269 }
271 =item B<getRRDArgs> (I<$index>)
273 Return the arguments needed to generate the graph from the RRD file(s). If the
274 file has only one data source, this default implementation will generate that
275 typical min, average, max graph you probably know from temperatures and such.
276 If the RRD files have multiple data sources, the average of each data source is
277 printes as simple line.
279 =cut
281 sub getRRDArgs
282 {
283 my $obj = shift;
284 my $index = shift;
286 my $ident = $obj->{'files'}[$index];
287 if (!$ident)
288 {
289 cluck ("Invalid index: $index");
290 return;
291 }
292 my $filename = ident_to_filename ($ident);
294 my $rrd_opts = $obj->{'rrd_opts'} || [];
295 my $rrd_title = $obj->getTitle ($ident);
296 my $format = $obj->{'rrd_format'} || '%5.1lf';
298 my $rrd_colors = $obj->{'rrd_colors'};
299 my @ret = ('-t', $rrd_title, @$rrd_opts);
301 if (defined $obj->{'rrd_vertical'})
302 {
303 push (@ret, '-v', $obj->{'rrd_vertical'});
304 }
306 my $ds_names = $obj->{'ds_names'};
307 if (!$ds_names)
308 {
309 $ds_names = {};
310 }
312 my $ds = $obj->getDataSources ();
313 if (!$ds)
314 {
315 confess ("obj->getDataSources failed.");
316 }
318 if (!$rrd_colors)
319 {
320 my @tmp = ('0000ff', 'ff0000', '00ff00', 'ff00ff', '00ffff', 'ffff00');
322 for (my $i = 0; $i < @$ds; $i++)
323 {
324 $rrd_colors->{$ds->[$i]} = $tmp[$i % @tmp];
325 }
326 }
328 for (my $i = 0; $i < @$ds; $i++)
329 {
330 my $f = $filename;
331 my $ds_name = $ds->[$i];
333 # We need to escape colons for RRDTool..
334 $f =~ s#:#\\:#g;
335 $ds_name =~ s#:#\\:#g;
337 push (@ret,
338 "DEF:min${i}=${f}:${ds_name}:MIN",
339 "DEF:avg${i}=${f}:${ds_name}:AVERAGE",
340 "DEF:max${i}=${f}:${ds_name}:MAX");
341 }
343 if (@$ds == 1)
344 {
345 my $ds_name = $ds->[0];
346 my $color_fg = $rrd_colors->{$ds_name} || '000000';
347 my $color_bg = get_faded_color ($color_fg);
349 if ($ds_names->{$ds_name})
350 {
351 $ds_name = $ds_names->{$ds_name};
352 }
353 $ds_name =~ s#:#\\:#g;
355 push (@ret,
356 "AREA:max0#${color_bg}",
357 "AREA:min0#${ColorCanvas}",
358 "LINE1:avg0#${color_fg}:${ds_name}",
359 "GPRINT:min0:MIN:${format} Min,",
360 "GPRINT:avg0:AVERAGE:${format} Avg,",
361 "GPRINT:max0:MAX:${format} Max,",
362 "GPRINT:avg0:LAST:${format} Last\\l");
363 }
364 else
365 {
366 for (my $i = 0; $i < @$ds; $i++)
367 {
368 my $ds_name = $ds->[$i];
369 my $color = $rrd_colors->{$ds_name} || '000000';
371 if ($ds_names->{$ds_name})
372 {
373 $ds_name = $ds_names->{$ds_name};
374 }
376 push (@ret,
377 "LINE1:avg${i}#${color}:${ds_name}",
378 "GPRINT:min${i}:MIN:${format} Min,",
379 "GPRINT:avg${i}:AVERAGE:${format} Avg,",
380 "GPRINT:max${i}:MAX:${format} Max,",
381 "GPRINT:avg${i}:LAST:${format} Last\\l");
382 }
383 }
385 return (\@ret);
386 } # getRRDArgs
388 =item B<getGraphArgs> (I<$index>)
390 Returns the parameters that should be passed to the CGI script to generate the
391 I<$index>th graph. The returned string is already URI-encoded and will possibly
392 set the "hostname", "plugin", "plugin_instance", "type", and "type_instance"
393 parameters.
395 The default implementation simply uses the ident of the I<$index>th file to
396 fill this.
398 =cut
400 sub getGraphArgs
401 {
402 my $obj = shift;
403 my $index = shift;
404 my $ident = $obj->{'files'}[$index];
406 my @args = ();
407 for (qw(hostname plugin plugin_instance type type_instance))
408 {
409 if (defined ($ident->{$_}))
410 {
411 push (@args, uri_escape ($_) . '=' . uri_escape ($ident->{$_}));
412 }
413 }
415 return (join (';', @args));
416 }
418 =item B<getLastModified> ([I<$index>])
420 If I<$index> is not given, the modification time of all files is scanned and the most recent modification is returned. If I<$index> is given, only the files belonging to the I<$index>th graph will be considered.
422 =cut
424 sub getLastModified
425 {
426 my $obj = shift;
427 my $index = @_ ? shift : -1;
429 my $mtime = 0;
431 if ($index == -1)
432 {
433 for (@{$obj->{'files'}})
434 {
435 my $ident = $_;
436 my $filename = ident_to_filename ($ident);
437 my @statbuf = stat ($filename);
439 if (!@statbuf)
440 {
441 next;
442 }
444 if ($mtime < $statbuf[9])
445 {
446 $mtime = $statbuf[9];
447 }
448 }
449 }
450 else
451 {
452 my $ident = $obj->{'files'}[$index];
453 my $filename = ident_to_filename ($ident);
454 my @statbuf = stat ($filename);
456 $mtime = $statbuf[9];
457 }
459 if (!$mtime)
460 {
461 return;
462 }
463 return ($mtime);
464 } # getLastModified
466 =back
468 =head1 SEE ALSO
470 L<Collectd::Graph::Type::GenericStacked>
472 =head1 AUTHOR AND LICENSE
474 Copyright (c) 2008 by Florian Forster
475 E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
476 General Public License, VersionE<nbsp>2 (GPLv2).
478 =cut
480 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :