turbostat: Fix parsing warnings
[collectd.git] / contrib / rrd_filter.px
1 #!/usr/bin/perl
2
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>
21
22 use strict;
23 use warnings;
24
25 =head1 NAME
26
27 rrd_filter.px - Perform same advanced non-standard operations on an RRD file.
28
29 =head1 SYNOPSYS
30
31   rrd_filter.px -i input.rrd -o output.rrd [options]
32
33 =head1 DEPENDENCIES
34
35 rrd_filter.px requires the RRDTool binary, Perl and the included
36 L<Getopt::Long> module.
37
38 =cut
39
40 use Getopt::Long ('GetOptions');
41
42 our $InFile;
43 our $InDS = [];
44 our $OutFile;
45 our $OutDS = [];
46
47 our $NewDSes = [];
48 our $NewRRAs = [];
49
50 our $Step = 0;
51
52 our $Scale = 1.0;
53 our $Shift = 0.0;
54
55 our $Debug = 0;
56
57 =head1 OPTIONS
58
59 The following options can be passed on the command line:
60
61 =over 4
62
63 =item B<--infile> I<file>
64
65 =item B<-i> I<file>
66
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.
70
71 =item B<--outfile> I<file>
72
73 =item B<-o> I<file>
74
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.
78
79 =item B<--map> I<in_ds>:I<out_ds>
80
81 =item B<-m> I<in_ds>:I<out_ds>
82
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.
85
86 =item B<--step> I<seconds>
87
88 =item B<-s> I<seconds>
89
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.
95
96 Examples:
97
98   step =  10, rra_steps = 12   =>   step = 60, rra_steps =  2
99   step = 300, rra_steps =  1   =>   step = 10, rra_steps = 30
100
101 =item B<--rra> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
102
103 =item B<-a> B<RRA>:I<CF>:I<XFF>:I<steps>:I<rows>
104
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)>.
108
109 =item B<--scale> I<factor>
110
111 Scales the values by the factor I<factor>, i.E<nbsp>e. all values are
112 multiplied by I<factor>.
113
114 =item B<--shift> I<offset>
115
116 Shifts all values by I<offset>, i.E<nbsp>e. I<offset> is added to all values.
117
118 =back
119
120 =cut
121
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);
160
161 if (!$InFile || !$OutFile)
162 {
163         print STDERR "Usage: $0 -i <infile> -m <in_ds>:<out_ds> -s <step>\n";
164         exit (1);
165 }
166 if ((1 + @$InDS) != (1 + @$OutDS))
167 {
168         print STDERR "You need the same amount of in- and out-DSes\n";
169         exit (1);
170 }
171 main ($InFile, $OutFile);
172 exit (0);
173
174 {
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
183 {
184         my $line = shift;
185         my $index = shift;
186         my $ret = '';
187
188         if ((@$InDS == 0) || (@$OutDS == 0))
189         {
190                 post_line ($line, $index + 1);
191                 return;
192         }
193
194         if (!defined ($state))
195         {
196                 $current_index = -1;
197                 $state = 0;
198                 $out_cache = [];
199
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         }
208
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);
223
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                         }
231
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                         }
257
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                         }
263
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                         }
301
302                         # Clear the cache
303                         for (my $i = 0; $i <= $current_index; $i++)
304                         {
305                                 $out_cache->[$i] = '';
306                         }
307
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";
342
343                         if ($line =~ m#(<!-- .*? -->)#)
344                         {
345                                 $ret .= "$1 ";
346                         }
347                         $ret .= "<row> ";
348
349                         $i = 0;
350                         while ($line =~ m#<v>\s*([^<\s]+)\s*</v>#g)
351                         {
352                                 $values[$i] = $1;
353                                 $i++;
354                         }
355
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         }
368
369         if ($ret)
370         {
371                 post_line ($ret, $index + 1);
372         }
373 }} # handle_line_dsmap
374
375 #
376 # The _step_ handler
377 #
378 {
379 my $step_factor_up;
380 my $step_factor_down;
381 sub handle_line_step
382 {
383         my $line = shift;
384         my $index = shift;
385
386         if (!$Step)
387         {
388                 post_line ($line, $index + 1);
389                 return;
390         }
391
392         if ($Debug && !defined ($step_factor_up))
393         {
394                 print STDOUT "New step: $Step\n";
395         }
396
397         $step_factor_up ||= 0;
398         $step_factor_down ||= 0;
399
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         }
454
455         post_line ($line, $index + 1);
456 }} # handle_line_step
457
458 #
459 # The _add DS_ handler
460 #
461 {
462 my $add_ds_done;
463 sub handle_line_add_ds
464 {
465   my $line = shift;
466   my $index = shift;
467
468   my $post = sub { for (@_) { post_line ($_, $index + 1); } };
469
470   if (!@$NewDSes)
471   {
472     $post->($line);
473     return;
474   }
475
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;
482
483       my $min;
484       my $max;
485
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       }
490
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       
503
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     }
518
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   }
535
536   $post->($line);
537 }} # handle_line_add_ds
538
539 #
540 # The _add RRA_ handler
541 #
542 {
543 my $add_rra_done;
544 my $num_ds;
545 sub handle_line_add_rra
546 {
547   my $line = shift;
548   my $index = shift;
549
550   my $post = sub { for (@_) { post_line ($_, $index + 1); } };
551
552   $num_ds ||= 0;
553
554   if (!@$NewRRAs || $add_rra_done)
555   {
556     $post->($line);
557     return;
558   }
559
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;
570
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       }
575
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");
583
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       }
593
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     }
602
603     $add_rra_done = 1;
604   }
605
606   $post->($line);
607 }} # handle_line_add_rra
608
609 #
610 # The _scale/shift_ handler
611 #
612 sub calculate_scale_shift 
613 {
614   my $value = shift;
615   my $tag = shift;
616   my $scale = shift;
617   my $shift = shift;
618
619   if (lc ("$value") eq 'nan')
620   {
621     $value = 'NaN';
622     return ("<$tag> NaN </$tag>");
623   }
624
625   $value = ($scale * (0.0 + $value)) + $shift;
626   return (sprintf ("<%s> %1.10e </%s>", $tag, $value, $tag));
627 }
628
629 sub handle_line_scale_shift
630 {
631   my $line = shift;
632   my $index = shift;
633
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   }
638
639   post_line ($line, $index + 1);
640 }
641
642 #
643 # The _output_ handler
644 #
645 # This filter is unfinished!
646 #
647 {
648 my $fh;
649 sub set_output
650 {
651         $fh = shift;
652 }
653
654 {
655 my $previous_values;
656 my $previous_differences;
657 my $pdp_per_row;
658 sub handle_line_peak_detect
659 {
660   my $line = shift;
661   my $index = shift;
662
663   if (!$previous_values)
664   {
665     $previous_values = [];
666     $previous_differences = [];
667   }
668
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     }
694
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   }
723
724   post_line ($line, $index + 1);
725 }} # handle_line_peak_detect
726
727 sub handle_line_output
728 {
729         my $line = shift;
730         my $index = shift;
731
732         if (!defined ($fh))
733         {
734                 post_line ($line, $index + 1);
735                 return;
736         }
737         
738         print $fh $line;
739 }} # handle_line_output
740
741 #
742 # Dispatching logic
743 #
744 {
745 my @handlers = ();
746 sub add_handler
747 {
748         my $handler = shift;
749
750         die unless (ref ($handler) eq 'CODE');
751         push (@handlers, $handler);
752 } # add_handler
753
754 sub post_line
755 {
756         my $line = shift;
757         my $index = shift;
758
759         if (0)
760         {
761                 my $copy = $line;
762                 chomp ($copy);
763                 print "DEBUG: post_line ($copy, $index);\n";
764         }
765
766         if ($index > $#handlers)
767         {
768                 return;
769         }
770         $handlers[$index]->($line, $index);
771 }} # post_line
772
773 sub handle_fh
774 {
775   my $in_fh = shift;
776   my $out_fh = shift;
777
778   set_output ($out_fh);
779
780   if (@$InDS)
781   {
782     add_handler (\&handle_line_dsmap);
783   }
784
785   if ($Step)
786   {
787     add_handler (\&handle_line_step);
788   }
789
790   if (($Scale != 1.0) || ($Shift != 0.0))
791   {
792     add_handler (\&handle_line_scale_shift);
793   }
794
795   #add_handler (\&handle_line_peak_detect);
796
797   if (@$NewDSes)
798   {
799     add_handler (\&handle_line_add_ds);
800   }
801
802   if (@$NewRRAs)
803   {
804     add_handler (\&handle_line_add_rra);
805   }
806
807   add_handler (\&handle_line_output);
808
809   while (my $line = <$in_fh>)
810   {
811     post_line ($line, 0);
812   }
813 } # handle_fh
814
815 sub main
816 {
817         my $in_file = shift;
818         my $out_file = shift;
819
820         my $in_fh;
821         my $out_fh;
822
823         my $in_needs_close = 1;
824         my $out_needs_close = 1;
825
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         }
839
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         }
853
854         handle_fh ($in_fh, $out_fh);
855
856         if ($in_needs_close)
857         {
858                 close ($in_fh);
859         }
860         if ($out_needs_close)
861         {
862                 close ($out_fh);
863         }
864 } # main
865
866 =head1 LICENSE
867
868 This script is licensed under the GNU general public license, versionE<nbsp>2
869 (GPLv2).
870
871 =head1 AUTHOR
872
873 Florian octo Forster E<lt>octo at verplant.orgE<gt>
874