Code

contrib/collection3: Add support for the “threads” and “total_requests” types.
[collectd.git] / contrib / collection3 / lib / Collectd / Graph / Type.pm
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
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
142   my $pkg = shift;
143   my $obj = bless ({files => []}, $pkg);
145   if (@_)
146   {
147     $obj->addFiles (@_);
148   }
150   return ($obj);
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
162   my $obj = shift;
163   push (@{$obj->{'files'}}, @_);
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
175   my $obj = shift;
176   return (scalar @{$obj->{'files'}});
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
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
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'};
251   my $instance;
253   if (defined $type_instance)
254   {
255     $instance = $type_instance;
256   }
257   elsif (defined $plugin_instance)
258   {
259     $instance = $plugin_instance;
260   }
261   else
262   {
263     $instance = 'no instance';
264   }
266   if (!defined $plugin_instance)
267   {
268     $plugin_instance = 'no instance';
269   }
271   if (!defined $type_instance)
272   {
273     $type_instance = 'no instance';
274   }
276   $title =~ s#{hostname}#$hostname#g;
277   $title =~ s#{plugin}#$plugin#g;
278   $title =~ s#{plugin_instance}#$plugin_instance#g;
279   $title =~ s#{type}#$type#g;
280   $title =~ s#{type_instance}#$type_instance#g;
281   $title =~ s#{instance}#$instance#g;
283   return ($title);
286 =item B<getRRDArgs> (I<$index>)
288 Return the arguments needed to generate the graph from the RRD file(s). If the
289 file has only one data source, this default implementation will generate that
290 typical min, average, max graph you probably know from temperatures and such.
291 If the RRD files have multiple data sources, the average of each data source is
292 printes as simple line.
294 =cut
296 sub getRRDArgs
298   my $obj = shift;
299   my $index = shift;
301   my $ident = $obj->{'files'}[$index];
302   if (!$ident)
303   {
304     cluck ("Invalid index: $index");
305     return;
306   }
307   my $filename = ident_to_filename ($ident);
309   my $rrd_opts = $obj->{'rrd_opts'} || [];
310   my $rrd_title = $obj->getTitle ($ident);
311   my $format = $obj->{'rrd_format'} || '%5.1lf';
313   my $rrd_colors = $obj->{'rrd_colors'};
314   my @ret = ('-t', $rrd_title, @$rrd_opts);
316   if (defined $obj->{'rrd_vertical'})
317   {
318     push (@ret, '-v', $obj->{'rrd_vertical'});
319   }
321   my $ds_names = $obj->{'ds_names'};
322   if (!$ds_names)
323   {
324     $ds_names = {};
325   }
327   my $ds = $obj->getDataSources ();
328   if (!$ds)
329   {
330     confess ("obj->getDataSources failed.");
331   }
333   if (!$rrd_colors)
334   {
335     my @tmp = ('0000ff', 'ff0000', '00ff00', 'ff00ff', '00ffff', 'ffff00');
337     for (my $i = 0; $i < @$ds; $i++)
338     {
339       $rrd_colors->{$ds->[$i]} = $tmp[$i % @tmp];
340     }
341   }
343   for (my $i = 0; $i < @$ds; $i++)
344   {
345     my $f = $filename;
346     my $ds_name = $ds->[$i];
348     # We need to escape colons for RRDTool..
349     $f =~ s#:#\\:#g;
350     $ds_name =~ s#:#\\:#g;
352     if (exists ($obj->{'scale'}))
353     {
354       my $scale = 0.0 + $obj->{'scale'};
355       push (@ret,
356         "DEF:min${i}_raw=${f}:${ds_name}:MIN",
357         "DEF:avg${i}_raw=${f}:${ds_name}:AVERAGE",
358         "DEF:max${i}_raw=${f}:${ds_name}:MAX",
359         "CDEF:max${i}=max${i}_raw,$scale,*",
360         "CDEF:avg${i}=avg${i}_raw,$scale,*",
361         "CDEF:min${i}=min${i}_raw,$scale,*");
362     }
363     else
364     {
365       push (@ret,
366         "DEF:min${i}=${f}:${ds_name}:MIN",
367         "DEF:avg${i}=${f}:${ds_name}:AVERAGE",
368         "DEF:max${i}=${f}:${ds_name}:MAX");
369     }
370   }
372   if (@$ds == 1)
373   {
374     my $ds_name = $ds->[0];
375     my $color_fg = $rrd_colors->{$ds_name} || '000000';
376     my $color_bg = get_faded_color ($color_fg);
378     if ($ds_names->{$ds_name})
379     {
380       $ds_name = $ds_names->{$ds_name};
381     }
382     $ds_name =~ s#:#\\:#g;
384     push (@ret, 
385       "AREA:max0#${color_bg}",
386       "AREA:min0#${ColorCanvas}",
387       "LINE1:avg0#${color_fg}:${ds_name}",
388       "GPRINT:min0:MIN:${format} Min,",
389       "GPRINT:avg0:AVERAGE:${format} Avg,",
390       "GPRINT:max0:MAX:${format} Max,",
391       "GPRINT:avg0:LAST:${format} Last\\l");
392   }
393   else
394   {
395     for (my $i = 0; $i < @$ds; $i++)
396     {
397       my $ds_name = $ds->[$i];
398       my $color = $rrd_colors->{$ds_name} || '000000';
400       if ($ds_names->{$ds_name})
401       {
402         $ds_name = $ds_names->{$ds_name};
403       }
405       push (@ret, 
406         "LINE1:avg${i}#${color}:${ds_name}",
407         "GPRINT:min${i}:MIN:${format} Min,",
408         "GPRINT:avg${i}:AVERAGE:${format} Avg,",
409         "GPRINT:max${i}:MAX:${format} Max,",
410         "GPRINT:avg${i}:LAST:${format} Last\\l");
411     }
412   }
414   return (\@ret);
415 } # getRRDArgs
417 =item B<getGraphArgs> (I<$index>)
419 Returns the parameters that should be passed to the CGI script to generate the
420 I<$index>th graph. The returned string is already URI-encoded and will possibly
421 set the "hostname", "plugin", "plugin_instance", "type", and "type_instance"
422 parameters.
424 The default implementation simply uses the ident of the I<$index>th file to
425 fill this.
427 =cut
429 sub getGraphArgs
431   my $obj = shift;
432   my $index = shift;
433   my $ident = $obj->{'files'}[$index];
435   my @args = ();
436   for (qw(hostname plugin plugin_instance type type_instance))
437   {
438     if (defined ($ident->{$_}))
439     {
440       push (@args, uri_escape ($_) . '=' . uri_escape ($ident->{$_}));
441     }
442   }
444   return (join (';', @args));
447 =item B<getLastModified> ([I<$index>])
449 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.
451 =cut
453 sub getLastModified
455   my $obj = shift;
456   my $index = @_ ? shift : -1;
458   my $mtime = 0;
460   if ($index == -1)
461   {
462     for (@{$obj->{'files'}})
463     {
464       my $ident = $_;
465       my $filename = ident_to_filename ($ident);
466       my @statbuf = stat ($filename);
468       if (!@statbuf)
469       {
470         next;
471       }
473       if ($mtime < $statbuf[9])
474       {
475         $mtime = $statbuf[9];
476       }
477     }
478   }
479   else
480   {
481     my $ident = $obj->{'files'}[$index];
482     my $filename = ident_to_filename ($ident);
483     my @statbuf = stat ($filename);
485     $mtime = $statbuf[9];
486   }
488   if (!$mtime)
489   {
490     return;
491   }
492   return ($mtime);
493 } # getLastModified
495 =back
497 =head1 SEE ALSO
499 L<Collectd::Graph::Type::GenericStacked>
501 =head1 AUTHOR AND LICENSE
503 Copyright (c) 2008 by Florian Forster
504 E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>. Licensed under the terms of the GNU
505 General Public License, VersionE<nbsp>2 (GPLv2).
507 =cut
509 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :