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);
112 }
114 sub filename_to_ident
115 {
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
144 {
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
188 {
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
209 {
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
254 {
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
282 {
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
336 {
337 my $host = sanitize_hostname (shift);
338 return (get_files_from_directory ("$DataDir/$host", 2));
339 } # get_files_for_host
341 sub _filter_ident
342 {
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
380 {
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
400 {
401 my $ident = {};
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
425 {
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
440 {
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
468 {
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
496 {
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
506 {
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
512 {
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
544 {
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
561 {
562 my $type = shift;
563 my $ret;
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
574 {
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);
583 }
585 # vim: set shiftwidth=2 softtabstop=2 tabstop=8 :