Code

Merge branch 'collectd-4.3' into collectd-4.4
[collectd.git] / contrib / collection3 / lib / Collectd / Graph / Common.pm
1 package Collectd::Graph::Common;
3 use strict;
4 use warnings;
6 use vars (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue));
8 use Carp (qw(confess cluck));
9 use CGI (':cgi');
10 use Exporter;
12 $ColorCanvas   = 'FFFFFF';
13 $ColorFullBlue = '0000FF';
14 $ColorHalfBlue = 'B7B7F7';
16 @Collectd::Graph::Common::ISA = ('Exporter');
17 @Collectd::Graph::Common::EXPORT_OK = (qw(
18   $ColorCanvas
19   $ColorFullBlue
20   $ColorHalfBlue
22   sanitize_hostname
23   sanitize_plugin sanitize_plugin_instance
24   sanitize_type sanitize_type_instance
25   group_files_by_plugin_instance
26   get_files_from_directory
27   filename_to_ident
28   ident_to_filename
29   ident_to_string
30   get_all_hosts
31   get_files_for_host
32   get_files_by_ident
33   get_selected_files
34   get_timespan_selection
35   get_host_selection
36   get_plugin_selection
37   get_faded_color
38   sort_idents_by_type_instance
39   type_to_module_name
40   epoch_to_rfc1123
41 ));
43 our $DataDir = '/var/lib/collectd/rrd';
45 return (1);
47 sub _sanitize_generic_allow_minus
48 {
49   my $str = "" . shift;
51   # remove all slashes
52   $str =~ s#/##g;
54   # remove all dots and dashes at the beginning and at the end.
55   $str =~ s#^[\.-]+##;
56   $str =~ s#[\.-]+$##;
58   return ($str);
59 }
61 sub _sanitize_generic_no_minus
62 {
63   # Do everything the allow-minus variant does..
64   my $str = _sanitize_generic_allow_minus (@_);
66   # .. and remove the dashes, too
67   $str =~ s#/##g;
69   return ($str);
70 } # _sanitize_generic_no_minus
72 sub sanitize_hostname
73 {
74   return (_sanitize_generic_allow_minus (@_));
75 }
77 sub sanitize_plugin
78 {
79   return (_sanitize_generic_no_minus (@_));
80 }
82 sub sanitize_plugin_instance
83 {
84   return (_sanitize_generic_allow_minus (@_));
85 }
87 sub sanitize_type
88 {
89   return (_sanitize_generic_no_minus (@_));
90 }
92 sub sanitize_type_instance
93 {
94   return (_sanitize_generic_allow_minus (@_));
95 }
97 sub group_files_by_plugin_instance
98 {
99   my @files = @_;
100   my $data = {};
102   for (my $i = 0; $i < @files; $i++)
103   {
104     my $file = $files[$i];
105     my $key = $file->{'plugin_instance'} || '';
107     $data->{$key} ||= [];
108     push (@{$data->{$key}}, $file);
109   }
111   return ($data);
114 sub filename_to_ident
116   my $file = shift;
117   my $ret;
119   if ($file =~ m#([^/]+)/([^/\-]+)(?:-([^/]+))?/([^/\-]+)(?:-([^/]+))?\.rrd$#)
120   {
121     $ret = {hostname => $1, plugin => $2, type => $4};
122     if (defined ($3))
123     {
124       $ret->{'plugin_instance'} = $3;
125     }
126     if (defined ($5))
127     {
128       $ret->{'type_instance'} = $5;
129     }
130     if ($`)
131     {
132       $ret->{'_prefix'} = $`;
133     }
134   }
135   else
136   {
137     return;
138   }
140   return ($ret);
141 } # filename_to_ident
143 sub ident_to_filename
145   my $ident = shift;
147   my $ret = '';
149   if (defined ($ident->{'_prefix'}))
150   {
151     $ret .= $ident->{'_prefix'};
152   }
153   else
154   {
155     $ret .= "$DataDir/";
156   }
158   if (!$ident->{'hostname'})
159   {
160     cluck ("hostname is undefined")
161   }
162   if (!$ident->{'plugin'})
163   {
164     cluck ("plugin is undefined")
165   }
166   if (!$ident->{'type'})
167   {
168     cluck ("type is undefined")
169   }
171   $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
172   if (defined ($ident->{'plugin_instance'}))
173   {
174     $ret .= '-' . $ident->{'plugin_instance'};
175   }
177   $ret .= '/' . $ident->{'type'};
178   if (defined ($ident->{'type_instance'}))
179   {
180     $ret .= '-' . $ident->{'type_instance'};
181   }
182   $ret .= '.rrd';
184   return ($ret);
185 } # ident_to_filename
187 sub ident_to_string
189   my $ident = shift;
191   my $ret = '';
193   $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
194   if (defined ($ident->{'plugin_instance'}))
195   {
196     $ret .= '-' . $ident->{'plugin_instance'};
197   }
199   $ret .= '/' . $ident->{'type'};
200   if (defined ($ident->{'type_instance'}))
201   {
202     $ret .= '-' . $ident->{'type_instance'};
203   }
205   return ($ret);
206 } # ident_to_string
208 sub get_files_from_directory
210   my $dir = shift;
211   my $recursive = @_ ? shift : 0;
212   my $dh;
213   my @directories = ();
214   my $ret = [];
216   opendir ($dh, $dir) or die ("opendir ($dir): $!");
217   while (my $entry = readdir ($dh))
218   {
219     next if ($entry =~ m/^\./);
221     $entry = "$dir/$entry";
223     if (-d $entry)
224     {
225       push (@directories, $entry);
226     }
227     elsif (-f $entry)
228     {
229       my $ident = filename_to_ident ($entry);
230       if ($ident)
231       {
232         push (@$ret, $ident);
233       }
234     }
235   }
236   closedir ($dh);
238   if ($recursive > 0)
239   {
240     for (@directories)
241     {
242       my $temp = get_files_from_directory ($_, $recursive - 1);
243       if ($temp && @$temp)
244       {
245         push (@$ret, @$temp);
246       }
247     }
248   }
250   return ($ret);
251 } # get_files_from_directory
253 sub get_all_hosts
255   my $dh;
256   my @ret = ();
258   opendir ($dh, "$DataDir") or confess ("opendir ($DataDir): $!");
259   while (my $entry = readdir ($dh))
260   {
261     next if ($entry =~ m/^\./);
262     next if (!-d "$DataDir/$entry");
263     push (@ret, sanitize_hostname ($entry));
264   }
265   closedir ($dh);
267   if (wantarray ())
268   {
269     return (@ret);
270   }
271   elsif (@ret)
272   {
273     return (\@ret);
274   }
275   else
276   {
277     return;
278   }
279 } # get_all_hosts
281 sub get_all_plugins
283   my @hosts = @_;
284   my $ret = {};
285   my $dh;
287   if (!@hosts)
288   {
289     @hosts = get_all_hosts ();
290   }
292   for (@hosts)
293   {
294     my $host = $_;
295     opendir ($dh, "$DataDir/$host") or next;
296     while (my $entry = readdir ($dh))
297     {
298       my $plugin;
299       my $plugin_instance = '';
301       next if ($entry =~ m/^\./);
302       next if (!-d "$DataDir/$host/$entry");
304       if ($entry =~ m#^([^-]+)-(.+)$#)
305       {
306         $plugin = $1;
307         $plugin_instance = $2;
308       }
309       elsif ($entry =~ m#^([^-]+)$#)
310       {
311         $plugin = $1;
312         $plugin_instance = '';
313       }
314       else
315       {
316         next;
317       }
319       $ret->{$plugin} ||= {};
320       $ret->{$plugin}{$plugin_instance} = 1;
321     } # while (readdir)
322     closedir ($dh);
323   } # for (@hosts)
325   if (wantarray ())
326   {
327     return (sort (keys %$ret));
328   }
329   else
330   {
331     return ($ret);
332   }
333 } # get_all_plugins
335 sub get_files_for_host
337   my $host = sanitize_hostname (shift);
338   return (get_files_from_directory ("$DataDir/$host", 2));
339 } # get_files_for_host
341 sub _filter_ident
343   my $filter = shift;
344   my $ident = shift;
346   for (qw(hostname plugin plugin_instance type type_instance))
347   {
348     my $part = $_;
349     my $tmp;
351     if (!defined ($filter->{$part}))
352     {
353       next;
354     }
355     if (!defined ($ident->{$part}))
356     {
357       return (1);
358     }
360     if (ref $filter->{$part})
361     {
362       if (!grep { $ident->{$part} eq $_ } (@{$filter->{$part}}))
363       {
364         return (1);
365       }
366     }
367     else
368     {
369       if ($ident->{$part} ne $filter->{$part})
370       {
371         return (1);
372       }
373     }
374   }
376   return (0);
377 } # _filter_ident
379 sub get_files_by_ident
381   my $ident = shift;
382   my $all_files;
383   my @ret = ();
385   #if ($ident->{'hostname'})
386   #{
387   #$all_files = get_files_for_host ($ident->{'hostname'});
388   #}
389   #else
390   #{
391     $all_files = get_files_from_directory ($DataDir, 3);
392     #}
394   @ret = grep { _filter_ident ($ident, $_) == 0 } (@$all_files);
396   return (\@ret);
397 } # get_files_by_ident
399 sub get_selected_files
401   my $ident = {};
402   
403   for (qw(hostname plugin plugin_instance type type_instance))
404   {
405     my $part = $_;
406     my @temp = param ($part);
407     if (!@temp)
408     {
409       next;
410     }
411     elsif (($part eq 'plugin') || ($part eq 'type'))
412     {
413       $ident->{$part} = [map { _sanitize_generic_no_minus ($_) } (@temp)];
414     }
415     else
416     {
417       $ident->{$part} = [map { _sanitize_generic_allow_minus ($_) } (@temp)];
418     }
419   }
421   return (get_files_by_ident ($ident));
422 } # get_selected_files
424 sub get_timespan_selection
426   my $ret = 86400;
427   if (param ('timespan'))
428   {
429     my $temp = int (param ('timespan'));
430     if ($temp && ($temp > 0))
431     {
432       $ret = $temp;
433     }
434   }
436   return ($ret);
437 } # get_timespan_selection
439 sub get_host_selection
441   my %ret = ();
443   for (get_all_hosts ())
444   {
445     $ret{$_} = 0;
446   }
448   for (param ('hostname'))
449   {
450     my $host = _sanitize_generic_allow_minus ($_);
451     if (defined ($ret{$host}))
452     {
453       $ret{$host} = 1;
454     }
455   }
457   if (wantarray ())
458   {
459     return (grep { $ret{$_} > 0 } (sort (keys %ret)));
460   }
461   else
462   {
463     return (\%ret);
464   }
465 } # get_host_selection
467 sub get_plugin_selection
469   my %ret = ();
470   my @hosts = get_host_selection ();
472   for (get_all_plugins (@hosts))
473   {
474     $ret{$_} = 0;
475   }
477   for (param ('plugin'))
478   {
479     if (defined ($ret{$_}))
480     {
481       $ret{$_} = 1;
482     }
483   }
485   if (wantarray ())
486   {
487     return (grep { $ret{$_} > 0 } (sort (keys %ret)));
488   }
489   else
490   {
491     return (\%ret);
492   }
493 } # get_plugin_selection
495 sub _string_to_color
497   my $color = shift;
498   if ($color =~ m/([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])([0-9A-Fa-f][0-9A-Fa-f])/)
499   {
500     return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
501   }
502   return;
503 } # _string_to_color
505 sub _color_to_string
507   confess ("Wrong number of arguments") if (@_ != 1);
508   return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
509 } # _color_to_string
511 sub get_faded_color
513   my $fg = shift;
514   my $bg;
515   my %opts = @_;
516   my $ret = [undef, undef, undef];
518   $opts{'background'} ||= [1.0, 1.0, 1.0];
519   $opts{'alpha'} ||= 0.25;
521   if (!ref ($fg))
522   {
523     $fg = _string_to_color ($fg)
524       or confess ("Cannot parse foreground color $fg");
525   }
527   if (!ref ($opts{'background'}))
528   {
529     $opts{'background'} = _string_to_color ($opts{'background'})
530       or confess ("Cannot parse background color " . $opts{'background'});
531   }
532   $bg = $opts{'background'};
534   for (my $i = 0; $i < 3; $i++)
535   {
536     $ret->[$i] = ($opts{'alpha'} * $fg->[$i])
537        + ((1.0 - $opts{'alpha'}) * $bg->[$i]);
538   }
540   return (_color_to_string ($ret));
541 } # get_faded_color
543 sub sort_idents_by_type_instance
545   my $idents = shift;
546   my $array_sort = shift;
548   my %elements = map { $_->{'type_instance'} => $_ } (@$idents);
549   splice (@$idents, 0);
551   for (@$array_sort)
552   {
553     next if (!exists ($elements{$_}));
554     push (@$idents, $elements{$_});
555     delete ($elements{$_});
556   }
557   push (@$idents, map { $elements{$_} } (sort (keys %elements)));
558 } # sort_idents_by_type_instance
560 sub type_to_module_name
562   my $type = shift;
563   my $ret;
564   
565   $ret = ucfirst (lc ($type));
567   $ret =~ s/[^A-Za-z_]//g;
568   $ret =~ s/_([A-Za-z])/\U$1\E/g;
570   return ("Collectd::Graph::Type::$ret");
571 } # type_to_module_name
573 sub epoch_to_rfc1123
575   my @days = (qw(Sun Mon Tue Wed Thu Fri Sat));
576   my @months = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
578   my $epoch = @_ ? shift : time ();
579   my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
580   my $string = sprintf ('%s, %02d %s %4d %02d:%02d:%02d GMT', $days[$wday], $mday,
581       $months[$mon], 1900 + $year, $hour ,$min, $sec);
582   return ($string);
585 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :