1 package Collectd::Graph::Common;
6 use vars (qw($ColorCanvas $ColorFullBlue $ColorHalfBlue));
8 use Collectd::Unixsock ();
9 use Carp (qw(confess cluck));
12 use Collectd::Graph::Config (qw(gc_get_scalar));
14 $ColorCanvas = 'FFFFFF';
15 $ColorFullBlue = '0000FF';
16 $ColorHalfBlue = 'B7B7F7';
18 @Collectd::Graph::Common::ISA = ('Exporter');
19 @Collectd::Graph::Common::EXPORT_OK = (qw(
25 sanitize_plugin sanitize_plugin_instance
26 sanitize_type sanitize_type_instance
27 group_files_by_plugin_instance
28 get_files_from_directory
36 get_timespan_selection
41 sort_idents_by_type_instance
47 our $DefaultDataDir = '/var/lib/collectd/rrd';
51 sub _sanitize_generic_allow_minus
58 # remove all dots and dashes at the beginning and at the end.
65 sub _sanitize_generic_no_minus
67 # Do everything the allow-minus variant does..
68 my $str = _sanitize_generic_allow_minus (@_);
70 # .. and remove the dashes, too
74 } # _sanitize_generic_no_minus
78 return (_sanitize_generic_allow_minus (@_));
83 return (_sanitize_generic_no_minus (@_));
86 sub sanitize_plugin_instance
88 return (_sanitize_generic_allow_minus (@_));
93 return (_sanitize_generic_no_minus (@_));
96 sub sanitize_type_instance
98 return (_sanitize_generic_allow_minus (@_));
101 sub group_files_by_plugin_instance
106 for (my $i = 0; $i < @files; $i++)
108 my $file = $files[$i];
109 my $key1 = $file->{'hostname'} || '';
110 my $key2 = $file->{'plugin_instance'} || '';
111 my $key = "$key1-$key2";
113 $data->{$key} ||= [];
114 push (@{$data->{$key}}, $file);
120 sub filename_to_ident
125 if ($file =~ m#([^/]+)/([^/\-]+)(?:-([^/]+))?/([^/\-]+)(?:-([^/]+))?\.rrd$#)
127 $ret = {hostname => $1, plugin => $2, type => $4};
130 $ret->{'plugin_instance'} = $3;
134 $ret->{'type_instance'} = $5;
138 $ret->{'_prefix'} = $`;
147 } # filename_to_ident
149 sub ident_to_filename
152 my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
156 if (defined ($ident->{'_prefix'}))
158 $ret .= $ident->{'_prefix'};
162 $ret .= "$data_dir/";
165 if (!$ident->{'hostname'})
167 cluck ("hostname is undefined")
169 if (!$ident->{'plugin'})
171 cluck ("plugin is undefined")
173 if (!$ident->{'type'})
175 cluck ("type is undefined")
178 $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
179 if (defined ($ident->{'plugin_instance'}))
181 $ret .= '-' . $ident->{'plugin_instance'};
184 $ret .= '/' . $ident->{'type'};
185 if (defined ($ident->{'type_instance'}))
187 $ret .= '-' . $ident->{'type_instance'};
192 } # ident_to_filename
200 $ret .= $ident->{'hostname'} . '/' . $ident->{'plugin'};
201 if (defined ($ident->{'plugin_instance'}))
203 $ret .= '-' . $ident->{'plugin_instance'};
206 $ret .= '/' . $ident->{'type'};
207 if (defined ($ident->{'type_instance'}))
209 $ret .= '-' . $ident->{'type_instance'};
215 sub get_files_from_directory
218 my $recursive = @_ ? shift : 0;
220 my @directories = ();
224 opendir ($dh, $dir) or die ("opendir ($dir): $!");
225 while (my $entry = readdir ($dh))
227 next if ($entry =~ m/^\./);
229 $entry = "$dir/$entry";
233 push (@directories, $entry);
237 push (@files, $entry);
242 push (@$ret, map { filename_to_ident ($_) } sort (@files));
248 my $temp = get_files_from_directory ($_, $recursive - 1);
251 push (@$ret, @$temp);
257 } # get_files_from_directory
263 my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
265 opendir ($dh, "$data_dir") or confess ("opendir ($data_dir): $!");
266 while (my $entry = readdir ($dh))
268 next if ($entry =~ m/^\./);
269 next if (!-d "$data_dir/$entry");
270 next if (!-r "$data_dir/$entry" or !-x "$data_dir/$entry");
271 push (@ret, sanitize_hostname ($entry));
294 my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
298 @hosts = get_all_hosts ();
304 opendir ($dh, "$data_dir/$host") or next;
305 while (my $entry = readdir ($dh))
308 my $plugin_instance = '';
310 next if ($entry =~ m/^\./);
311 next if (!-d "$data_dir/$host/$entry");
313 if ($entry =~ m#^([^-]+)-(.+)$#)
316 $plugin_instance = $2;
318 elsif ($entry =~ m#^([^-]+)$#)
321 $plugin_instance = '';
328 $ret->{$plugin} ||= {};
329 $ret->{$plugin}{$plugin_instance} = 1;
336 return (sort (keys %$ret));
344 sub get_files_for_host
346 my $host = sanitize_hostname (shift);
347 my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
348 return (get_files_from_directory ("$data_dir/$host", 2));
349 } # get_files_for_host
356 for (qw(hostname plugin plugin_instance type type_instance))
361 if (!defined ($filter->{$part}))
365 if (!defined ($ident->{$part}))
370 if (ref $filter->{$part})
372 if (!grep { $ident->{$part} eq $_ } (@{$filter->{$part}}))
379 if ($ident->{$part} ne $filter->{$part})
389 sub get_files_by_ident
394 my $data_dir = gc_get_scalar ('DataDir', $DefaultDataDir);
396 #if ($ident->{'hostname'})
398 #$all_files = get_files_for_host ($ident->{'hostname'});
402 $all_files = get_files_from_directory ($data_dir, 3);
405 @ret = grep { _filter_ident ($ident, $_) == 0 } (@$all_files);
408 } # get_files_by_ident
410 sub get_selected_files
414 for (qw(hostname plugin plugin_instance type type_instance))
417 my @temp = param ($part);
422 elsif (($part eq 'plugin') || ($part eq 'type'))
424 $ident->{$part} = [map { _sanitize_generic_no_minus ($_) } (@temp)];
428 $ident->{$part} = [map { _sanitize_generic_allow_minus ($_) } (@temp)];
432 return (get_files_by_ident ($ident));
433 } # get_selected_files
435 sub get_timespan_selection
438 if (param ('timespan'))
440 my $temp = int (param ('timespan'));
441 if ($temp && ($temp > 0))
448 } # get_timespan_selection
450 sub get_host_selection
454 for (get_all_hosts ())
459 for (param ('hostname'))
461 my $host = _sanitize_generic_allow_minus ($_);
462 if (defined ($ret{$host}))
470 return (grep { $ret{$_} > 0 } (sort (keys %ret)));
476 } # get_host_selection
478 sub get_plugin_selection
481 my @hosts = get_host_selection ();
483 for (get_all_plugins (@hosts))
488 for (param ('plugin'))
490 if (defined ($ret{$_}))
498 return (grep { $ret{$_} > 0 } (sort (keys %ret)));
504 } # get_plugin_selection
509 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])/)
511 return ([hex ($1) / 255.0, hex ($2) / 255.0, hex ($3) / 255.0]);
518 confess ("Wrong number of arguments") if (@_ != 1);
519 return (sprintf ('%02hx%02hx%02hx', map { int (255.0 * $_) } @{$_[0]}));
524 my ($r, $g, $b) = (rand (), rand ());
530 $min = 1.0 - ($r + $g);
534 $max = 2.0 - ($r + $g);
537 $b = $min + (rand () * ($max - $min));
539 return (_color_to_string ([$r, $g, $b]));
547 my $ret = [undef, undef, undef];
549 $opts{'background'} ||= [1.0, 1.0, 1.0];
550 $opts{'alpha'} ||= 0.25;
554 $fg = _string_to_color ($fg)
555 or confess ("Cannot parse foreground color $fg");
558 if (!ref ($opts{'background'}))
560 $opts{'background'} = _string_to_color ($opts{'background'})
561 or confess ("Cannot parse background color " . $opts{'background'});
563 $bg = $opts{'background'};
565 for (my $i = 0; $i < 3; $i++)
567 $ret->[$i] = ($opts{'alpha'} * $fg->[$i])
568 + ((1.0 - $opts{'alpha'}) * $bg->[$i]);
571 return (_color_to_string ($ret));
574 sub sort_idents_by_type_instance
577 my $array_sort = shift;
579 my %elements = map { $_->{'type_instance'} => $_ } (@$idents);
580 splice (@$idents, 0);
584 next if (!exists ($elements{$_}));
585 push (@$idents, $elements{$_});
586 delete ($elements{$_});
588 push (@$idents, map { $elements{$_} } (sort (keys %elements)));
589 } # sort_idents_by_type_instance
591 sub type_to_module_name
596 $ret = ucfirst (lc ($type));
598 $ret =~ s/[^A-Za-z_]//g;
599 $ret =~ s/_([A-Za-z])/\U$1\E/g;
601 return ("Collectd::Graph::Type::$ret");
602 } # type_to_module_name
606 my @days = (qw(Sun Mon Tue Wed Thu Fri Sat));
607 my @months = (qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
609 my $epoch = @_ ? shift : time ();
610 my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch);
611 my $string = sprintf ('%s, %02d %s %4d %02d:%02d:%02d GMT', $days[$wday], $mday,
612 $months[$mon], 1900 + $year, $hour ,$min, $sec);
618 my $all_files = shift;
627 my $files_to_flush = [];
630 if (!defined $opts{'begin'})
632 cluck ("begin is not defined");
635 $begin = $opts{'begin'};
637 if (!defined $opts{'end'})
639 cluck ("end is not defined");
649 $interval = $opts{'interval'} || 10;
651 if (ref ($all_files) eq 'HASH')
653 my @tmp = ($all_files);
658 # Don't flush anything if the timespan is in the future.
659 if (($end > $now) && ($begin > $now))
667 my $file_name = ident_to_filename ($file_orig);
672 @statbuf = stat ($file_name);
677 $mtime = $statbuf[9];
679 # Skip if file is fresh
680 if (($now - $mtime) <= $interval)
684 # or $end is before $mtime
685 elsif (($end != 0) && (($end - $mtime) <= 0))
690 $file_copy->{'host'} = $file_orig->{'hostname'};
691 $file_copy->{'plugin'} = $file_orig->{'plugin'};
692 if (exists $file_orig->{'plugin_instance'})
694 $file_copy->{'plugin_instance'} = $file_orig->{'plugin_instance'}
696 $file_copy->{'type'} = $file_orig->{'type'};
697 if (exists $file_orig->{'type_instance'})
699 $file_copy->{'type_instance'} = $file_orig->{'type_instance'}
702 push (@$files_to_flush, $file_copy);
703 } # for (@$all_files)
705 if (!@$files_to_flush)
710 $sock = Collectd::Unixsock->new ($opts{'addr'});
716 $status = $sock->flush (plugins => ['rrdtool'], identifier => $files_to_flush);
719 cluck ("FLUSH failed: " . $sock->{'error'});
728 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :