Code

Merge remote-tracking branch 'github/pr/387'
[collectd.git] / contrib / rrd_filter.px
1 #!/usr/bin/perl
3 # collectd - contrib/rrd_filter.px
4 # Copyright (C) 2007-2008  Florian octo Forster
5 #
6 # This program is free software; you can redistribute it and/or modify it
7 # under the terms of the GNU General Public License as published by the
8 # Free Software Foundation; only version 2 of the License is applicable.
9 #
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18 #
19 # Authors:
20 #   Florian octo Forster <octo at verplant.org>
22 use strict;
23 use warnings;
25 =head1 NAME
27 rrd_filter.px - Perform same advanced non-standard operations on an RRD file.
29 =head1 SYNOPSYS
31   rrd_filter.px -i input.rrd -o output.rrd [options]
33 =head1 DEPENDENCIES
35 rrd_filter.px requires the RRDTool binary, Perl and the included
36 L<Getopt::Long> module.
38 =cut
40 use Getopt::Long ('GetOptions');
42 our $InFile;
43 our $InDS = [];
44 our $OutFile;
45 our $OutDS = [];
47 our $NewDSes = [];
48 our $NewRRAs = [];
50 our $Step = 0;
52 our $Scale = 1.0;
53 our $Shift = 0.0;
55 our $Debug = 0;
57 =head1 OPTIONS
59 The following options can be passed on the command line:
61 =over 4
63 =item B<--infile> I<file>
65 =item B<-i> I<file>
67 Reads from I<file>. If I<file> ends in C<.rrd>, then C<rrdtool dump> is invoked
68 to create an XML dump of the RRD file. Otherwise the XML dump is expected
69 directly. The special filename C<-> can be used to read from STDIN.
71 =item B<--outfile> I<file>
73 =item B<-o> I<file>
75 Writes output to I<file>. If I<file> ends in C<.rrd>, then C<rrdtool restore>
76 is invoked to create a binary RRD file. Otherwise an XML output is written. The
77 special filename C<-> can be used to write to STDOUT.
79 =item B<--map> I<in_ds>:I<out_ds>
81 =item B<-m> I<in_ds>:I<out_ds>
83 Writes the datasource I<in_ds> to the output and renames it to I<out_ds>. This
84 is useful to extract one DS from an RRD file.
86 =item B<--step> I<seconds>
88 =item B<-s> I<seconds>
90 Changes the step of the output RRD file to be I<seconds>. The new stepsize must
91 be a multiple of the old stepsize of the other way around. When increasing the
92 stepsize the number of PDPs in each RRA must be dividable by the factor by
93 which the stepsize is increased. The length of CDPs and the absolute length of
94 RRAs (and thus the data itself) is not altered.
96 Examples:
98   step =  10, rra_steps = 12   =>   step = 60, rra_steps =  2
99   step = 300, rra_steps =  1   =>   step = 10, rra_steps = 30
101 =item B<--rra> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
103 =item B<-a> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
105 Inserts a new RRA in the generated RRD file. This is done B<after> the step has
106 been adjusted, take that into account when specifying I<steps> and I<rows>. For
107 an explanation of the format please see L<rrdcreate(1)>.
109 =item B<--scale> I<factor>
111 Scales the values by the factor I<factor>, i.E<nbsp>e. all values are
112 multiplied by I<factor>.
114 =item B<--shift> I<offset>
116 Shifts all values by I<offset>, i.E<nbsp>e. I<offset> is added to all values.
118 =back
120 =cut
122 GetOptions ("infile|i=s" => \$InFile,
123         "outfile|o=s" => \$OutFile,
124         'map|m=s' => sub
125         {
126                 my ($in_ds, $out_ds) = split (':', $_[1]);
127                 if (!defined ($in_ds) || !defined ($out_ds))
128                 {
129                         print STDERR "Argument for `map' incorrect! The format is `--map in_ds:out_ds'\n";
130                         exit (1);
131                 }
132                 push (@$InDS, $in_ds);
133                 push (@$OutDS, $out_ds);
134         },
135         'step|s=i' => \$Step,
136         'ds|d=s' => sub
137         {
138                 #DS:ds-name:GAUGE | COUNTER | DERIVE | ABSOLUTE:heartbeat:min:max
139                 my ($ds, $name, $type, $hb, $min, $max) = split (':', $_[1]);
140                 if (($ds ne 'DS') || !defined ($max))
141                 {
142                         print STDERR "Please use the standard RRDTool syntax when adding DSes. I. e. DS:<name>:<type>:<heartbeat>:<min>:<max>.\n";
143                         exit (1);
144                 }
145                 push (@$NewDSes, {name => $name, type => $type, heartbeat => $hb, min => $min, max => $max});
146         },
147         'rra|a=s' => sub
148         {
149                 my ($rra, $cf, $xff, $steps, $rows) = split (':', $_[1]);
150                 if (($rra ne 'RRA') || !defined ($rows))
151                 {
152                         print STDERR "Please use the standard RRDTool syntax when adding RRAs. I. e. RRA:<cf><xff>:<steps>:<rows>.\n";
153                         exit (1);
154                 }
155                 push (@$NewRRAs, {cf => $cf, xff => $xff, steps => $steps, rows => $rows});
156         },
157         'scale=f' => \$Scale,
158         'shift=f' => \$Shift
159 ) or exit (1);
161 if (!$InFile || !$OutFile)
163         print STDERR "Usage: $0 -i <infile> -m <in_ds>:<out_ds> -s <step>\n";
164         exit (1);
166 if ((1 + @$InDS) != (1 + @$OutDS))
168         print STDERR "You need the same amount of in- and out-DSes\n";
169         exit (1);
171 main ($InFile, $OutFile);
172 exit (0);
175 my $ds_index;
176 my $current_index;
177 # state 0 == searching for DS index
178 # state 1 == parse RRA header
179 # state 2 == parse values
180 my $state;
181 my $out_cache;
182 sub handle_line_dsmap
184         my $line = shift;
185         my $index = shift;
186         my $ret = '';
188         if ((@$InDS == 0) || (@$OutDS == 0))
189         {
190                 post_line ($line, $index + 1);
191                 return;
192         }
194         if (!defined ($state))
195         {
196                 $current_index = -1;
197                 $state = 0;
198                 $out_cache = [];
200                 # $ds_index->[new_index] = old_index
201                 $ds_index = [];
202                 for (my $i = 0; $i < @$InDS; $i++)
203                 {
204                         print STDOUT "DS map $i: $InDS->[$i] -> $OutDS->[$i]\n" if ($Debug);
205                         $ds_index->[$i] = -1;
206                 }
207         }
209         if ($state == 0)
210         {
211                 if ($line =~ m/<ds>/)
212                 {
213                         $current_index++;
214                         $out_cache->[$current_index] = $line;
215                 }
216                 elsif ($line =~ m#<name>\s*([^<\s]+)\s*</name>#)
217                 {
218                         # old_index == $current_index
219                         # new_index == $i
220                         for (my $i = 0; $i < @$InDS; $i++)
221                         {
222                                 next if ($ds_index->[$i] >= 0);
224                                 if ($1 eq $InDS->[$i])
225                                 {
226                                         $line =~ s#<name>\s*([^<\s]+)\s*</name>#<name> $OutDS->[$i] </name>#;
227                                         $ds_index->[$i] = $current_index;
228                                         last;
229                                 }
230                         }
232                         $out_cache->[$current_index] .= $line;
233                 }
234                 elsif ($line =~ m#<last_ds>\s*([^\s>]+)\s*</last_ds>#i)
235                 {
236                         $out_cache->[$current_index] .= "\t\t<last_ds> NaN </last_ds>\n";
237                 }
238                 elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
239                 {
240                         $out_cache->[$current_index] .= "\t\t<value> NaN </value>\n";
241                 }
242                 elsif ($line =~ m#</ds>#)
243                 {
244                         $out_cache->[$current_index] .= $line;
245                 }
246                 elsif ($line =~ m#<rra>#)
247                 {
248                         # Print out all the DS definitions we need
249                         for (my $new_index = 0; $new_index < @$InDS; $new_index++)
250                         {
251                                 my $old_index = $ds_index->[$new_index];
252                                 while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
253                                 {
254                                         post_line ("$1\n", $index + 1);
255                                 }
256                         }
258                         # Clear the cache - it's used in state1, too.
259                         for (my $i = 0; $i <= $current_index; $i++)
260                         {
261                                 $out_cache->[$i] = '';
262                         }
264                         $ret .= $line;
265                         $current_index = -1;
266                         $state = 1;
267                 }
268                 elsif ($current_index == -1)
269                 {
270                         # Print all the lines before the first DS definition
271                         $ret .= $line;
272                 }
273                 else
274                 {
275                         # Something belonging to a DS-definition
276                         $out_cache->[$current_index] .= $line;
277                 }
278         }
279         elsif ($state == 1)
280         {
281                 if ($line =~ m#<ds>#)
282                 {
283                         $current_index++;
284                         $out_cache->[$current_index] .= $line;
285                 }
286                 elsif ($line =~ m#<value>\s*([^\s>]+)\s*</value>#i)
287                 {
288                         $out_cache->[$current_index] .= "\t\t\t<value> NaN </value>\n";
289                 }
290                 elsif ($line =~ m#</cdp_prep>#)
291                 {
292                         # Print out all the DS definitions we need
293                         for (my $new_index = 0; $new_index < @$InDS; $new_index++)
294                         {
295                                 my $old_index = $ds_index->[$new_index];
296                                 while ($out_cache->[$old_index] =~ m/^(.*)$/gm)
297                                 {
298                                         post_line ("$1\n", $index + 1);
299                                 }
300                         }
302                         # Clear the cache
303                         for (my $i = 0; $i <= $current_index; $i++)
304                         {
305                                 $out_cache->[$i] = '';
306                         }
308                         $ret .= $line;
309                         $current_index = -1;
310                 }
311                 elsif ($line =~ m#<database>#)
312                 {
313                         $ret .= $line;
314                         $state = 2;
315                 }
316                 elsif ($current_index == -1)
317                 {
318                         # Print all the lines before the first DS definition
319                         # and after cdp_prep
320                         $ret .= $line;
321                 }
322                 else
323                 {
324                         # Something belonging to a DS-definition
325                         $out_cache->[$current_index] .= $line;
326                 }
327         }
328         elsif ($state == 2)
329         {
330                 if ($line =~ m#</database>#)
331                 {
332                         $ret .= $line;
333                         $current_index = -1;
334                         $state = 1;
335                 }
336                 else
337                 {
338                         my @values = ();
339                         my $i;
340                         
341                         $ret .= "\t\t";
343                         if ($line =~ m#(<!-- .*? -->)#)
344                         {
345                                 $ret .= "$1 ";
346                         }
347                         $ret .= "<row> ";
349                         $i = 0;
350                         while ($line =~ m#<v>\s*([^<\s]+)\s*</v>#g)
351                         {
352                                 $values[$i] = $1;
353                                 $i++;
354                         }
356                         for (my $new_index = 0; $new_index < @$InDS; $new_index++)
357                         {
358                                 my $old_index = $ds_index->[$new_index];
359                                 $ret .= '<v> ' . $values[$old_index] . ' </v> ';
360                         }
361                         $ret .= "</row>\n";
362                 }
363         }
364         else
365         {
366                 die;
367         }
369         if ($ret)
370         {
371                 post_line ($ret, $index + 1);
372         }
373 }} # handle_line_dsmap
376 # The _step_ handler
379 my $step_factor_up;
380 my $step_factor_down;
381 sub handle_line_step
383         my $line = shift;
384         my $index = shift;
386         if (!$Step)
387         {
388                 post_line ($line, $index + 1);
389                 return;
390         }
392         if ($Debug && !defined ($step_factor_up))
393         {
394                 print STDOUT "New step: $Step\n";
395         }
397         $step_factor_up ||= 0;
398         $step_factor_down ||= 0;
400         if (($step_factor_up == 0) && ($step_factor_down == 0))
401         {
402                 if ($line =~ m#<step>\s*(\d+)\s*</step>#i)
403                 {
404                         my $old_step = 0 + $1;
405                         if ($Step < $old_step)
406                         {
407                                 $step_factor_down = int ($old_step / $Step);
408                                 if (($step_factor_down * $Step) != $old_step)
409                                 {
410                                         print STDERR "The old step ($old_step seconds) "
411                                         . "is not a multiple of the new step "
412                                         . "($Step seconds).\n";
413                                         exit (1);
414                                 }
415                                 $line = "<step> $Step </step>\n";
416                         }
417                         elsif ($Step > $old_step)
418                         {
419                                 $step_factor_up = int ($Step / $old_step);
420                                 if (($step_factor_up * $old_step) != $Step)
421                                 {
422                                         print STDERR "The new step ($Step seconds) "
423                                         . "is not a multiple of the old step "
424                                         . "($old_step seconds).\n";
425                                         exit (1);
426                                 }
427                                 $line = "<step> $Step </step>\n";
428                         }
429                         else
430                         {
431                                 $Step = 0;
432                         }
433                 }
434         }
435         elsif ($line =~ m#<pdp_per_row>\s*(\d+)\s*</pdp_per_row>#i)
436         {
437                 my $old_val = 0 + $1;
438                 my $new_val;
439                 if ($step_factor_up)
440                 {
441                         $new_val = int ($old_val / $step_factor_up);
442                         if (($new_val * $step_factor_up) != $old_val)
443                         {
444                                 print STDERR "Can't divide number of PDPs per row ($old_val) by step-factor ($step_factor_up).\n";
445                                 exit (1);
446                         }
447                 }
448                 else
449                 {
450                         $new_val = $step_factor_down * $old_val;
451                 }
452                 $line = "<pdp_per_row> $new_val </pdp_per_row>\n";
453         }
455         post_line ($line, $index + 1);
456 }} # handle_line_step
459 # The _add DS_ handler
462 my $add_ds_done;
463 sub handle_line_add_ds
465   my $line = shift;
466   my $index = shift;
468   my $post = sub { for (@_) { post_line ($_, $index + 1); } };
470   if (!@$NewDSes)
471   {
472     $post->($line);
473     return;
474   }
476   if (!$add_ds_done && ($line =~ m#<rra>#i))
477   {
478     for (my $i = 0; $i < @$NewDSes; $i++)
479     {
480       my $ds = $NewDSes->[$i];
481       my $temp;
483       my $min;
484       my $max;
486       if ($Debug)
487       {
488         print STDOUT "Adding DS: name = $ds->{'name'}, type = $ds->{'type'}, heartbeat = $ds->{'heartbeat'}, min = $ds->{'min'}, max = $ds->{'max'}\n";
489       }
491       $min = 'NaN';
492       if (defined ($ds->{'min'}) && ($ds->{'min'} ne 'U'))
493       {
494         $min = sprintf ('%.10e', $ds->{'min'});
495       }
496       
497       $max = 'NaN';
498       if (defined ($ds->{'max'}) && ($ds->{'max'} ne 'U'))
499       {
500         $max = sprintf ('%.10e', $ds->{'max'});
501       }
502       
504       $post->("\t<ds>\n",
505       "\t\t<name> $ds->{'name'} </name>\n",
506       "\t\t<type> $ds->{'type'} </type>\n",
507       "\t\t<minimal_heartbeat> $ds->{'heartbeat'} </minimal_heartbeat>\n",
508       "\t\t<min> $min </min>\n",
509       "\t\t<max> $max </max>\n",
510       "\n",
511       "\t\t<!-- PDP Status -->\n",
512       "\t\t<last_ds> UNKN </last_ds>\n",
513       "\t\t<value> NaN </value>\n",
514       "\t\t<unknown_sec> 0 </unknown_sec>\n",
515       "\t</ds>\n",
516       "\n");
517     }
519     $add_ds_done = 1;
520   }
521   elsif ($add_ds_done && ($line =~ m#</ds>#i)) # inside a cdp_prep block
522   {
523     $post->("\t\t\t</ds>\n",
524         "\t\t\t<ds>\n",
525         "\t\t\t<primary_value> NaN </primary_value>\n",
526         "\t\t\t<secondary_value> NaN </secondary_value>\n",
527         "\t\t\t<value> NaN </value>\n",
528         "\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n");
529   }
530   elsif ($line =~ m#<row>#i)
531   {
532           my $insert = '<v> NaN </v>' x (0 + @$NewDSes);
533           $line =~ s#</row>#$insert</row>#i;
534   }
536   $post->($line);
537 }} # handle_line_add_ds
540 # The _add RRA_ handler
543 my $add_rra_done;
544 my $num_ds;
545 sub handle_line_add_rra
547   my $line = shift;
548   my $index = shift;
550   my $post = sub { for (@_) { post_line ($_, $index + 1); } };
552   $num_ds ||= 0;
554   if (!@$NewRRAs || $add_rra_done)
555   {
556     $post->($line);
557     return;
558   }
560   if ($line =~ m#<ds>#i)
561   {
562     $num_ds++;
563   }
564   elsif ($line =~ m#<rra>#i)
565   {
566     for (my $i = 0; $i < @$NewRRAs; $i++)
567     {
568       my $rra = $NewRRAs->[$i];
569       my $temp;
571       if ($Debug)
572       {
573         print STDOUT "Adding RRA: CF = $rra->{'cf'}, xff = $rra->{'xff'}, steps = $rra->{'steps'}, rows = $rra->{'rows'}, num_ds = $num_ds\n";
574       }
576       $post->("\t<rra>\n",
577       "\t\t<cf> $rra->{'cf'} </cf>\n",
578       "\t\t<pdp_per_row> $rra->{'steps'} </pdp_per_row>\n",
579       "\t\t<params>\n",
580       "\t\t\t<xff> $rra->{'xff'} </xff>\n",
581       "\t\t</params>\n",
582       "\t\t<cdp_prep>\n");
584       for (my $j = 0; $j < $num_ds; $j++)
585       {
586         $post->("\t\t\t<ds>\n",
587         "\t\t\t\t<primary_value> NaN </primary_value>\n",
588         "\t\t\t\t<secondary_value> NaN </secondary_value>\n",
589         "\t\t\t\t<value> NaN </value>\n",
590         "\t\t\t\t<unknown_datapoints> 0 </unknown_datapoints>\n",
591         "\t\t\t</ds>\n");
592       }
594       $post->("\t\t</cdp_prep>\n", "\t\t<database>\n");
595       $temp = "\t\t\t<row>" . join ('', map { "<v> NaN </v>" } (1 .. $num_ds)) . "</row>\n";
596       for (my $j = 0; $j < $rra->{'rows'}; $j++)
597       {
598         $post->($temp);
599       }
600       $post->("\t\t</database>\n", "\t</rra>\n");
601     }
603     $add_rra_done = 1;
604   }
606   $post->($line);
607 }} # handle_line_add_rra
610 # The _scale/shift_ handler
612 sub calculate_scale_shift 
614   my $value = shift;
615   my $tag = shift;
616   my $scale = shift;
617   my $shift = shift;
619   if (lc ("$value") eq 'nan')
620   {
621     $value = 'NaN';
622     return ("<$tag> NaN </$tag>");
623   }
625   $value = ($scale * (0.0 + $value)) + $shift;
626   return (sprintf ("<%s> %1.10e </%s>", $tag, $value, $tag));
629 sub handle_line_scale_shift
631   my $line = shift;
632   my $index = shift;
634   if (($Scale != 1.0) || ($Shift != 0.0))
635   {
636     $line =~ s#<(min|max|last_ds|value|primary_value|secondary_value|v)>\s*([^\s<]+)\s*</[^>]+>#calculate_scale_shift ($2, $1, $Scale, $Shift)#eg;
637   }
639   post_line ($line, $index + 1);
643 # The _output_ handler
645 # This filter is unfinished!
648 my $fh;
649 sub set_output
651         $fh = shift;
655 my $previous_values;
656 my $previous_differences;
657 my $pdp_per_row;
658 sub handle_line_peak_detect
660   my $line = shift;
661   my $index = shift;
663   if (!$previous_values)
664   {
665     $previous_values = [];
666     $previous_differences = [];
667   }
669   if ($line =~ m#</database>#i)
670   {
671     $previous_values = [];
672     $previous_differences = [];
673     print STDERR "==============================================================================\n";
674   }
675   elsif ($line =~ m#<pdp_per_row>\s*([1-9][0-9]*)\s*</pdp_per_row>#)
676   {
677     $pdp_per_row = int ($1);
678     print STDERR "pdp_per_row = $pdp_per_row;\n";
679   }
680   elsif ($line =~ m#<row>#)
681   {
682     my @values = ();
683     while ($line =~ m#<v>\s*([^\s>]+)\s*</v>#ig)
684     {
685       if ($1 eq 'NaN')
686       {
687         push (@values, undef);
688       }
689       else
690       {
691         push (@values, 0.0 + $1);
692       }
693     }
695     for (my $i = 0; $i < @values; $i++)
696     {
697       if (!defined ($values[$i]))
698       {
699         $previous_values->[$i] = undef;
700       }
701       elsif (!defined ($previous_values->[$i]))
702       {
703         $previous_values->[$i] = $values[$i];
704       }
705       elsif (!defined ($previous_differences->[$i]))
706       {
707         $previous_differences->[$i] = abs ($previous_values->[$i] - $values[$i]);
708       }
709       else
710       {
711         my $divisor = ($previous_differences->[$i] < 1.0) ? 1.0 : $previous_differences->[$i];
712         my $difference = abs ($previous_values->[$i] - $values[$i]);
713         my $change = $pdp_per_row * $difference / $divisor;
714         if (($divisor > 10.0) &&  ($change > 10e5))
715         {
716           print STDERR "i = $i; average difference = " . $previous_differences->[$i]. "; current difference = " . $difference. "; change = $change;\n";
717         }
718         $previous_values->[$i] = $values[$i];
719         $previous_differences->[$i] = (0.95 * $previous_differences->[$i]) + (0.05 * $difference);
720       }
721     }
722   }
724   post_line ($line, $index + 1);
725 }} # handle_line_peak_detect
727 sub handle_line_output
729         my $line = shift;
730         my $index = shift;
732         if (!defined ($fh))
733         {
734                 post_line ($line, $index + 1);
735                 return;
736         }
737         
738         print $fh $line;
739 }} # handle_line_output
742 # Dispatching logic
745 my @handlers = ();
746 sub add_handler
748         my $handler = shift;
750         die unless (ref ($handler) eq 'CODE');
751         push (@handlers, $handler);
752 } # add_handler
754 sub post_line
756         my $line = shift;
757         my $index = shift;
759         if (0)
760         {
761                 my $copy = $line;
762                 chomp ($copy);
763                 print "DEBUG: post_line ($copy, $index);\n";
764         }
766         if ($index > $#handlers)
767         {
768                 return;
769         }
770         $handlers[$index]->($line, $index);
771 }} # post_line
773 sub handle_fh
775   my $in_fh = shift;
776   my $out_fh = shift;
778   set_output ($out_fh);
780   if (@$InDS)
781   {
782     add_handler (\&handle_line_dsmap);
783   }
785   if ($Step)
786   {
787     add_handler (\&handle_line_step);
788   }
790   if (($Scale != 1.0) || ($Shift != 0.0))
791   {
792     add_handler (\&handle_line_scale_shift);
793   }
795   #add_handler (\&handle_line_peak_detect);
797   if (@$NewDSes)
798   {
799     add_handler (\&handle_line_add_ds);
800   }
802   if (@$NewRRAs)
803   {
804     add_handler (\&handle_line_add_rra);
805   }
807   add_handler (\&handle_line_output);
809   while (my $line = <$in_fh>)
810   {
811     post_line ($line, 0);
812   }
813 } # handle_fh
815 sub main
817         my $in_file = shift;
818         my $out_file = shift;
820         my $in_fh;
821         my $out_fh;
823         my $in_needs_close = 1;
824         my $out_needs_close = 1;
826         if ($in_file =~ m/\.rrd$/i)
827         {
828                 open ($in_fh,  '-|', 'rrdtool', 'dump', $in_file) or die ("open (rrdtool): $!");
829         }
830         elsif ($in_file eq '-')
831         {
832                 $in_fh = \*STDIN;
833                 $in_needs_close = 0;
834         }
835         else
836         {
837                 open ($in_fh, '<', $in_file) or die ("open ($in_file): $!");
838         }
840         if ($out_file =~ m/\.rrd$/i)
841         {
842                 open ($out_fh, '|-', 'rrdtool', 'restore', '-', $out_file) or die ("open (rrdtool): $!");
843         }
844         elsif ($out_file eq '-')
845         {
846                 $out_fh = \*STDOUT;
847                 $out_needs_close = 0;
848         }
849         else
850         {
851                 open ($out_fh, '>', $out_file) or die ("open ($out_file): $!");
852         }
854         handle_fh ($in_fh, $out_fh);
856         if ($in_needs_close)
857         {
858                 close ($in_fh);
859         }
860         if ($out_needs_close)
861         {
862                 close ($out_fh);
863         }
864 } # main
866 =head1 LICENSE
868 This script is licensed under the GNU general public license, versionE<nbsp>2
869 (GPLv2).
871 =head1 AUTHOR
873 Florian octo Forster E<lt>octo at verplant.orgE<gt>