#! /usr/local/bin/perl5 ######################################################################### # Copyright (C) 2001 by Jason Fesler. # # # # This code may be freely used and distributed so long as this # # notice is preserved. # # # # THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS `AS IS'' # # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, # # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A # # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR # # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, # # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, # # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY # # OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ######################################################################### ###################################################### # $Log: add_ds_rra.pl,v $ # Revision 1.4 2001/03/06 22:54:52 jfesler # Added Log: # ###################################################### =head1 NAME add_ds_rra.pl =head1 SYNOPSIS add_ds_rra.pl -in test.rrd -out out.rrd DS:Test:COUNTER:300:NaN:NaN add_ds_rra.pl -in test.rrd -out out.rrd RRA:LAST:.5:1:10 =head1 DESCRIPTION This script will add one or more DS's or RRA's (or both!) very quickly. It can either write to a new RRD file, or (at your descrition) replace existing RRD files. Some error checking and parameter is done, but it is not perfect. It should catch common errors, such as typos on data source types and consolidating function names. =head1 OPTIONS =item -in filename Specify the input file with -in . If you wish to use stdin, specify "-". =item -out filename Specify the output file wiht -out. If you wish to use stdout, specify "-". If you wish to replace the input file with the revised output, simply specify the same name. If the conversion is successful, the original will be removed and the replacement moved into place. =item DS:ds-name:DST:heartbeat:min:max (Adding DS values) If you wish to add DS values, specify them in the same fashion as rrdtool's "create" command. You may specify more than one DS value if you wish. =item RRA:CF:xff:steps:rows If you wish to add additional RRA's (round robin archives), specify them in the same fashion as rrdtool's "create"command. You may specify more than one additional RRA if you wish. =head1 EXAMPLES add_ds_rra.pl -in test.rrd -out out.rrd DS:Test:COUNTER:300:NaN:NaN add_ds_rra.pl -in test.rrd -out out.rrd RRA:LAST:.5:1:10 =head1 SECURITY No known security concerns in this type of application. However, there is a concern that if the format of the RRD file changes, this script will not adequately work. The cookies written by RRD are checked to validate that we understand the file format. =head1 BUGS The %pack hash describing the structure of the data is geared towards Solaris 7. It may not be quite right for other platforms. If there are variances I would like to know about them to possibly code in proper handling. =head1 AUTHOR Jason Fesler Ejfesler@gigo.comE =cut use Getopt::Long; use strict 'vars'; # Defines use vars qw( $DNAN $DINF $TIME_OK $RRD_COOKIE $RRD_VERSION $FLOAT_COOKIE $DS_NAM_FMT $DS_NAM_SIZE $DST_FMT $DST_SIZE $CF_NAM_FMT $CF_NAM_SIZE $LAST_DS_LEN); # Packing use vars qw( %pack %pack_string %pack_vars %pack_eats); # Enumerated types use vars qw( %timetype %dst_en %ds_param_en %cf_en %rra_par_en %rra_param_en %pdp_par_en %cdp_par_en ); use vars qw( %inv_timetype %inv_dst_en %inv_ds_param_en %inv_cf_en %inv_rra_par_en %inv_rra_param_en %inv_pdp_par_en %inv_cdp_par_en ); # Others use vars qw( %argv $debug %existing_ds $new_ds_cnt $new_rra_cnt $output_offset $input_offset @NEWDS @NEWRRA ); ###################################################### # Start of code # ###################################################### &init; &getopts; ###################################################### # Open files # ###################################################### my($buffer,$buffer_new); my($tempfile) = "$argv{out}.new.$$"; END{ if (defined $tempfile) { unlink($tempfile);}} open(INFILE,"$argv{in}") || die "Could not open for read: $argv{in}: $!"; open(OUTFILE,">$tempfile") || die "Could not open for write: $tempfile: $!"; ###################################################### # stat_head_t : read, write modified # ###################################################### print "Reading stat_head_t\n" if ($argv{'v'}); my(%stat_head_t, %stat_head_t_new); $buffer = readin($pack_eats{"stat_head_t"},"stat_head_t"); %stat_head_t = unpack_hash("stat_head_t",$buffer); %stat_head_t_new = %stat_head_t; $stat_head_t_new{"ds_cnt"} += scalar @NEWDS; $stat_head_t_new{"rra_cnt"} += scalar @NEWRRA; $buffer_new = &pack_hash("stat_head_t",%stat_head_t_new); if ($argv{"v"}) { print "Setting ds_cnt from $stat_head_t{ds_cnt} to $stat_head_t_new{ds_cnt}\n"; print "Setting rra_cnt from $stat_head_t{rra_cnt} to $stat_head_t_new{rra_cnt}\n"; } writeout($buffer_new,"stat_head_t"); ##################################################################### # While debugging, use this to tell me what offsets # # are expected. When in debug mode each read/write will # # show a reason for the read/write as well as the resulting offset. # ##################################################################### if ($debug) { my($o) = 0; print "****EXPECTED OFFSETS FOR SOURCE DATABASE (based on the header)****\n"; print sprintf("%-6s %s\n",$o,"stat_head_t"); $o += $pack_eats{"stat_head_t"}; print sprintf("%-6s %s\n",$o,"ds_def_t"); $o += $pack_eats{"ds_def_t"} * $stat_head_t{"ds_cnt"}; print sprintf("%-6s %s\n",$o,"rra_def_t"); $o += $pack_eats{"rra_def_t"} * $stat_head_t{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"live_head_t"); $o += $pack_eats{"live_head_t"}; print sprintf("%-6s %s\n",$o,"pdp_prep_t"); $o += $pack_eats{"pdp_prep_t"} * $stat_head_t{"ds_cnt"}; print sprintf("%-6s %s\n",$o,"cdp_prep_t"); $o += $pack_eats{"cdp_prep_t"} * $stat_head_t{"ds_cnt"} * $stat_head_t{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"rra_ptr_t"); $o += $pack_eats{"rra_ptr_t"} * $stat_head_t{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"data"); print "\n\n"; $o=0; print "****EXPECTED OFFSETS FOR DEST DATABASE (based on the header)****\n"; print sprintf("%-6s %s\n",$o,"stat_head_t"); $o += $pack_eats{"stat_head_t"}; print sprintf("%-6s %s\n",$o,"ds_def_t"); $o += $pack_eats{"ds_def_t"} * $stat_head_t_new{"ds_cnt"}; print sprintf("%-6s %s\n",$o,"rra_def_t"); $o += $pack_eats{"rra_def_t"} * $stat_head_t_new{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"live_head_t"); $o += $pack_eats{"live_head_t"}; print sprintf("%-6s %s\n",$o,"pdp_prep_t"); $o += $pack_eats{"pdp_prep_t"} * $stat_head_t_new{"ds_cnt"}; print sprintf("%-6s %s\n",$o,"cdp_prep_t"); $o += $pack_eats{"cdp_prep_t"} * $stat_head_t_new{"ds_cnt"} * $stat_head_t_new{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"rra_ptr_t"); $o += $pack_eats{"rra_ptr_t"} * $stat_head_t_new{"rra_cnt"}; print sprintf("%-6s %s\n",$o,"data"); print "\n\n"; } ###################################################### # Check magic cookies # ###################################################### unless ($stat_head_t{"cookie"} eq $RRD_COOKIE) { die("rrd cookie doesn't match; expected $RRD_COOKIE got $stat_head_t{cookie}"); } unless ($stat_head_t{"version"} eq $RRD_VERSION) { die("rrd version doesn't match; expected $RRD_VERSION got $stat_head_t{version}"); } unless ($stat_head_t{"float_cookie"} eq $FLOAT_COOKIE) { die("float_cookie doesn't match; expected $FLOAT_COOKIE got $stat_head_t{float_cookie} - Are you running this for the right platform? This may be an alignment problem."); } ###################################################################### # Read all ds_def_t's and write them to the output file immediately. # # Also, parse them, to get a list of current DS names, as well # # as to set default values for anything the user did not specifiy. # ###################################################################### my %ds; foreach (1..$stat_head_t{"ds_cnt"}) { print "Writing ds_def_t for ds $_\n" if ($argv{'v'}); $buffer=readin($pack_eats{"ds_def_t"},"ds_def_t $_"); writeout($buffer,"ds_def_t $_"); %ds = unpack_hash("ds_def_t",$buffer); $existing_ds{ $ds{"Name"} }++; } ###################################################### # Write extra ds_def_t's. # ###################################################### foreach (@NEWDS) { %ds = (); ($ds{"Name"},$ds{"Type"},$ds{"DS_mrhb_cnt"}, $ds{"DS_min_val"},$ds{"DS_max_val"}) = split(/:/); # Translate U (Unknown) to NaN $ds{"DS_mrhb_cnt"} =~ s/^U$/NaN/i; $ds{"DS_min_val"} =~ s/^U$/NaN/i; $ds{"DS_max_val"} =~ s/^U$/NaN/i; unless (defined $dst_en{"DST_" . $ds{"Type"}}) { die("ERROR for $_: DST ($ds{Type}) should be one of " . join(",",grep(s/DST_//o,sort keys %dst_en))); } if ($existing_ds{$ds{"Name"}}++) { die("ERROR for $_ : $ds{Name} would be listed more than once"); } unless ($ds{"Name"} =~ m#^$DS_NAM_FMT$#) { die("ERROR for $_ : invalid name format; should be DS_NAM_FMT"); } $buffer = pack_hash("ds_def_t", %ds); writeout($buffer,"ds_def_t $_"); } ####################################################### # Copy all of the RRA data verbatim. Not much reason # # to even investigate this. # ####################################################### my %rra; print "Copying rra_def_t headers\n" if ($argv{"v"}); foreach (1..$stat_head_t{"rra_cnt"}) { $buffer=readin($pack_eats{"rra_def_t"},"rra_def_t $_"); writeout($buffer,"rra_def_t $_"); %{$rra{$_}}=unpack_hash("rra_def_t",$buffer); # Needed to get # of rows per RRA } # If we add code for doing new RRA's, code needs to go here. my($o)=0; foreach (@NEWRRA) { my(%r); %r = (); ($r{"Consolidation"},$r{"RRA_cdp_xff_val"},$r{"pdp_cnt"}, $r{"row_cnt"}) = split(/:/); $r{"RRA_cdp_xff_val"} =~ s#^U$#NaN#i; unless (($r{"pdp_cnt"} > 0) && ($r{"pdp_cnt"} =~ m/^\d+$/)) { die "ERROR for $_ : steps must be an integer > 0\n"; } unless (($r{"row_cnt"} > 0) && ($r{"row_cnt"} =~ m/^\d+$/)) { die "ERROR for $_ : rows must be an integer > 0\n"; } $buffer = pack_hash("rra_def_t",%r); writeout($buffer,"rra_def_t for $_"); %{$rra{$stat_head_t{"rra_cnt"} + $o++}} = %r; } ###################################################### # copy live_head_t ; we are not going to update it. # ###################################################### print "Copying live_head_t\n" if ($argv{'v'}); $buffer = readin( $pack_eats{"live_head_t"}, "live_head_t"); writeout($buffer,"live_head_t"); ###################################################### # copy pdp_prep_t once for each existing DS # ###################################################### print "Copying ds_prep_t\n" if ($argv{'v'}); foreach (1..$stat_head_t{"ds_cnt"}) { $buffer=readin( $pack_eats{"pdp_prep_t"}, "pdp_prep_t $_"); writeout($buffer,"pdp_prep_t $_"); unpack_hash("pdp_prep_t",$buffer) if ($debug); # Debugging and compare against rrdtool's dump } # Make some new pdp_prep_t's as well - one per new DS # my %pdp; foreach (@NEWDS) { print "Writing new pdp_prep_t header for $_\n" if ($argv{"v"}); %pdp = (); $pdp{"PDP_unkn_sec_cnt"} = "0"; $pdp{"PDP_val"} = "NaN"; $buffer = pack_hash("pdp_prep_t",%pdp); writeout($buffer,"pdp_prep_t $_"); } ###################################################### # data prep area for cdp values # ###################################################### print "Copying cdp_prep_t\n" if ($argv{'v'}); foreach (1..$stat_head_t{"rra_cnt"} * $stat_head_t{"ds_cnt"}) { $buffer=readin( $pack_eats{"cdp_prep_t"}, "cdp_prep_t $_"); writeout($buffer,"cdp_prep_t $_"); unpack_hash("cdp_prep_t",$buffer) if ($debug); # Debugging and compare against rrdtool's dump } # Make some new cdp_prep_t's as well - one per new DS, per RRA # my %cdp; %cdp = (); $pdp{"CDP_val"} = "NaN"; $pdp{"CDP_unkn_pdp_cnt"} = "0"; $buffer = pack_hash("cdp_prep_t",%pdp); $buffer = $buffer x ( ($stat_head_t_new{"rra_cnt"} * $stat_head_t_new{"ds_cnt"}) - ($stat_head_t{"rra_cnt"} * $stat_head_t{"ds_cnt"}) ); writeout($buffer,"cdp_prep_t extras"); ######################################################### # rra_ptr_t - pointers to the current row in each RRA # ######################################################### print "Copying rra_ptr_t\n" if ($argv{'v'}); foreach (1..$stat_head_t{"rra_cnt"}) { $buffer=readin( $pack_eats{"rra_ptr_t"}, "rra_ptr_t $_"); writeout($buffer,"rra_ptr_t $_"); unpack_hash("rra_ptr_t",$buffer) if ($debug); # Debugging and compare against rrdtool's dump } foreach (@NEWRRA) { my (%h)=(); $h{"curr_row"} = 0; $buffer = pack_hash("rra_ptr_t",%h); writeout($buffer,"rra_ptr_t $_"); } ################################################################ # Whew. That's the entire header. Now, it's time to write out # # the RRA's. # # # # foreach (@rra list) # # foreach (@rows) # # foreach (ds list) # # print # # foreach (new ds list) # # print # # # ################################################################ print "Copying raw data (including padding for new ds's)\n" if ($argv{'v'}); my ($rra_number,$row_number); my ($blanks) = pack("d","NaN") x $new_ds_cnt; my ($readsize) = $pack_eats{"u_val"} * $stat_head_t{"ds_cnt"}; foreach $rra_number (1..$stat_head_t{"rra_cnt"}) { foreach $row_number (1.. $rra{$rra_number}{"row_cnt"}) { # Don't use our readin/writeout buffers - this loop is expensive and we don't need the debugging. if ($debug) { $buffer = readin($readsize,"rra_number $rra_number row_number $row_number"); $buffer .= $blanks; writeout($buffer,"rra_number $rra_number row_number $row_number"); } else { read INFILE,$buffer,$readsize; print OUTFILE $buffer,$blanks; } } } # Did we have any new RRA's? $blanks = pack("d","NaN") x $stat_head_t_new{"ds_cnt"}; $o = $stat_head_t{"rra_cnt"}; foreach (@NEWRRA) { my($b) = $blanks x ($rra{$o++}{"row_cnt"}); writeout($b,"Empty RRA for $_"); } ########################################################################### # Files are done being read/written. # # Close files, rm the target, and attempt to mv the temp file into place. # ########################################################################### close INFILE; close OUTFILE; unlink($argv{"out"}); rename($tempfile,$argv{"out"}) || die "Could not rename $tempfile to $argv{out}: $!"; ########################################################## # initialization stuff # # here is where I translated rrd_*.h into something that # # could be applied via perl # ########################################################## sub init { $debug=0; $DNAN = NaN; # We use a DNAN to represent the UNKNOWN $DINF = Infinity; # We use a DINF to represent a value at # the upper or lower border of the graph $TIME_OK = 0; # Beats me $RRD_COOKIE = "RRD"; $RRD_VERSION = "0001"; $FLOAT_COOKIE = 8.642135E130; $pack{"rrd_value_t"} = "d"; &enumhash("timetype",qw(ABSOLUTE_TIME RELATIVE_TO_START_TIME RELATIVE_TO_END_TIME)); $pack{"tm"} = " i # tm_sec i # tm_min i # tm_hour i # tm_mday i # tm_mon i # tm_year i # tm_wday i # tm_yday i # tm_isdst "; $pack{"time_value"} = " l # timetype (enumerated) (4 byte alignment) l # offset (long) $pack{tm} # tm "; # union unival: $pack{"unival"} = "a8 "; $pack{"u_cnt"} = "L"; $pack{"u_val"} = $pack{"rrd_value_t"}; ################################################################################### # /**************************************************************************** # # * The RRD Database Structure # # * --------------------------- # # * # # * In oder to properly describe the database structure lets define a few # # * new words: # # * # # * ds - Data Source (ds) providing input to the database. A Data Source (ds) # # * can be a traffic counter, a temperature, the number of users logged # # * into a system. The rrd database format can handle the input of # # * several Data Sources (ds) in a singe database. # # * # # * dst - Data Source Type (dst). The Data Source Type (dst) defines the rules # # * applied to Build Primary Data Points from the input provided by the # # * data sources (ds). # # * # # * pdp - Primary Data Point (pdp). After the database has accepted the # # * input from the data sources (ds). It starts building Primary # # * Data Points (pdp) from the data. Primary Data Points (pdp) # # * are evenly spaced along the time axis (pdp_step). The values # # * of the Primary Data Points are calculated from the values of # # * the data source (ds) and the exact time these values were # # * provided by the data source (ds). # # * # # * pdp_st - PDP Start (pdp_st). The moments (pdp_st) in time where # # * these steps occur are defined by the moments where the # # * number of seconds since 1970-jan-1 modulo pdp_step equals # # * zero (pdp_st). # # * # # * cf - Consolidation Function (cf). An arbitrary Consolidation Function (cf) # # * (averaging, min, max) is applied to the primary data points (pdp) to # # * calculate the consolidated data point. # # * # # * cdp - Consolidated Data Point (cdp) is the long term storage format for data # # * in the rrd database. Consolidated Data Points represent one or # # * several primary data points collected along the time axis. The # # * Consolidated Data Points (cdp) are stored in Round Robin Archives # # * (rra). # # * # # * rra - Round Robin Archive (rra). This is the place where the # # * consolidated data points (cdp) get stored. The data is # # * organized in rows (row) and columns (col). The Round Robin # # * Archive got its name from the method data is stored in # # * there. An RRD database can contain several Round Robin # # * Archives. Each Round Robin Archive can have a different row # # * spacing along the time axis (pdp_cnt) and a different # # * consolidation function (cf) used to build its consolidated # # * data points (cdp). # # * # # * rra_st - RRA Start (rra_st). The moments (rra_st) in time where # # * Consolidated Data Points (cdp) are added to an rra are # # * defined by the moments where the number of seconds since # # * 1970-jan-1 modulo pdp_cnt*pdp_step equals zero (rra_st). # # * # # * row - Row (row). A row represent all consolidated data points (cdp) # # * in a round robin archive who are of the same age. # # * # # * col - Column (col). A column (col) represent all consolidated # # * data points (cdp) in a round robin archive (rra) who # # * originated from the same data source (ds). # # * # # */ # ################################################################################### ################################################################################## # # # /**************************************************************************** # # * POS 1: stat_head_t static header of the database # # ****************************************************************************/ # # # # typedef struct stat_head_t { # # # # /* Data Base Identification Section ***/ # # char cookie[4]; /* RRD */ # # char version[5]; /* version of the format */ # # double float_cookie; /* is it the correct double # # * representation ? */ # # # # /* Data Base Structure Definition *****/ # # unsigned long ds_cnt; /* how many different ds provide # # * input to the rrd */ # # unsigned long rra_cnt; /* how many rras will be maintained # # * in the rrd */ # # unsigned long pdp_step; /* pdp interval in seconds */ # # # # unival par[10]; /* global parameters ... unused # # at the moment */ # # } stat_head_t; # # # ################################################################################## $pack{"stat_head_t"} = " A4 # cookie A5 # version A7 # fill1 d # float_cookie L # ds_cnt L # rra_cnt L # pdp_step A4 # fill2 A80 # unused "; ################################################################################## # # # /**************************************************************************** # # * POS 2: ds_def_t (* ds_cnt) Data Source definitions # # ****************************************************************************/ # ################################################################################## # enum dst_en &enumhash("dst_en",qw(DST_COUNTER DST_ABSOLUTE DST_GAUGE DST_DERIVE)); &enumhash("ds_param_en",qw(DS_mrhb_cnt DS_min_val DS_max_val)); # /* The magic number here is one less than DS_NAM_SIZE */ $DS_NAM_FMT = "[a-zA-Z0-9_-]{1,19}"; $DS_NAM_SIZE = 20; $DST_FMT = "[A-Z]{1,19}"; $DST_SIZE = 20; # struct ds_def_t $pack{"ds_def_t"} = " A${DS_NAM_SIZE} # Name of the data source (null terminated) A${DST_SIZE} # Type of data source (null terminated) $pack{u_cnt} # $inv_ds_param_en{0} A4 # filler0 $pack{u_val} # $inv_ds_param_en{1} $pack{u_val} # $inv_ds_param_en{2} $pack{unival} # $inv_ds_param_en{3} $pack{unival} # $inv_ds_param_en{4} $pack{unival} # $inv_ds_param_en{5} $pack{unival} # $inv_ds_param_en{6} $pack{unival} # $inv_ds_param_en{7} $pack{unival} # $inv_ds_param_en{8} $pack{unival} # $inv_ds_param_en{9} "; ################################################################################## # # # /**************************************************************************** # # * POS 3: rra_def_t ( * rra_cnt) one for each store to be maintained # # ****************************************************************************/ # # # ################################################################################## # enum cf_en # /* data consolidation functions */ &enumhash("cf_en",qw(CF_AVERAGE CF_MINIMUM CF_MAXIMUM CF_LAST)); &enumhash("rra_par_en",qw(RRA_cdp_xff_val)); $CF_NAM_FMT = "[A-Z]{1,19}"; $CF_NAM_SIZE = 20; $pack{"rra_def_t"} = " A${CF_NAM_SIZE} # Consolidation function (null term) L # row_cnt - number of entries in the store L # pdp_cnt - how many primary data points are required for a consolidated data point? A4 # filler1 $pack{u_val} # $inv_rra_par_en{0} $pack{unival} # $inv_rra_par_en{1} $pack{unival} # $inv_rra_par_en{2} $pack{unival} # $inv_rra_par_en{3} $pack{unival} # $inv_rra_par_en{4} $pack{unival} # $inv_rra_par_en{5} $pack{unival} # $inv_rra_par_en{6} $pack{unival} # $inv_rra_par_en{7} $pack{unival} # $inv_rra_par_en{8} $pack{unival} # $inv_rra_par_en{9} "; ################################################################################## # # # /**************************************************************************** # # **************************************************************************** # # **************************************************************************** # # * LIVE PART OF THE HEADER. THIS WILL BE WRITTEN ON EVERY UPDATE * # # **************************************************************************** # # **************************************************************************** # # ****************************************************************************/ # # /**************************************************************************** # # * POS 4: live_head_t # # ****************************************************************************/ # # # ################################################################################## $pack{"time_t"} = "L"; $pack{"live_head_t"} = " $pack{time_t} # last_up - when was rrd last updated "; ################################################################################## # /**************************************************************************** # # * POS 5: pdp_prep_t (* ds_cnt) here we prepare the pdps # # ****************************************************************************/ # ################################################################################## $LAST_DS_LEN=30; # /* DO NOT CHANGE THIS ... */ &enumhash("pdp_par_en",qw(PDP_unkn_sec_cnt PDP_val)); $pack{"pdp_prep_t"} = " A${LAST_DS_LEN} # last_ds - the last reading from the data source. this is stored in ASCII to cater for very large counters we might encounter in connection with SNMP. A2 # filler1 $pack{u_cnt} # $inv_pdp_par_en{0} A4 # filler0 $pack{u_val} # $inv_pdp_par_en{1} $pack{unival} # $inv_pdp_par_en{2} $pack{unival} # $inv_pdp_par_en{3} $pack{unival} # $inv_pdp_par_en{4} $pack{unival} # $inv_pdp_par_en{5} $pack{unival} # $inv_pdp_par_en{6} $pack{unival} # $inv_pdp_par_en{7} $pack{unival} # $inv_pdp_par_en{8} $pack{unival} # $inv_pdp_par_en{9} "; ##################################################################################### # # # /* data is passed from pdp to cdp when seconds since epoch modulo pdp_step == 0 # # obviously the updates do not occur at these times only. Especially does the # # format allow for updates to occur at different times for each data source. # # The rules which makes this work is as follows: # # # # * DS updates may only occur at ever increasing points in time # # * When any DS update arrives after a cdp update time, the *previous* # # update cycle gets executed. All pdps are transfered to cdps and the # # cdps feed the rras where necessary. Only then the new DS value # # is loaded into the PDP. */ # # # ##################################################################################### ################################################################################## # # # /**************************************************************************** # # * POS 6: cdp_prep_t (* rra_cnt * ds_cnt ) data prep area for cdp values # # ****************************************************************************/ # ################################################################################## &enumhash("cdp_par_en",qw(CDP_val CDP_unkn_pdp_cnt)); $pack{"cdp_prep_t"} = " $pack{u_val} # $inv_cdp_par_en{0} $pack{u_val} # $inv_cdp_par_en{1} $pack{unival} # $inv_cdp_par_en{2} $pack{unival} # $inv_cdp_par_en{3} $pack{unival} # $inv_cdp_par_en{4} $pack{unival} # $inv_cdp_par_en{5} $pack{unival} # $inv_cdp_par_en{6} $pack{unival} # $inv_cdp_par_en{7} $pack{unival} # $inv_cdp_par_en{8} $pack{unival} # $inv_cdp_par_en{9} "; ################################################################################## # /**************************************************************************** # # * POS 7: rra_ptr_t (* rra_cnt) pointers to the current row in each rra # # ****************************************************************************/ # ################################################################################## $pack{"rra_ptr_t"} = " L # cur_row - current row in the RRA "; ################################################################################## # # # /**************************************************************************** # # **************************************************************************** # # * One single struct to hold all the others. For convenience. # # **************************************************************************** # # ****************************************************************************/ # ################################################################################## ################################################################################### # typedef struct rrd_t { # # stat_head_t *stat_head; /* the static header */ # # ds_def_t *ds_def; /* list of data source definitions */ # # rra_def_t *rra_def; /* list of round robin archive def */ # # live_head_t *live_head; # # pdp_prep_t *pdp_prep; /* pdp data prep area */ # # cdp_prep_t *cdp_prep; /* cdp prep area */ # # rra_ptr_t *rra_ptr; /* list of rra pointers */ # # rrd_value_t *rrd_value; /* list of rrd values */ # # } rrd_t; # # # # /**************************************************************************** # # **************************************************************************** # # * AFTER the header section we have the DATA STORAGE AREA it is made up from # # * Consolidated Data Points organized in Round Robin Archives. # # **************************************************************************** # # **************************************************************************** # # # # *RRA 0 # # (0,0) .................... ( ds_cnt -1 , 0) # # . # # . # # . # # (0, row_cnt -1) ... (ds_cnt -1, row_cnt -1) # # # # *RRA 1 # # *RRA 2 # # # # *RRA rra_cnt -1 # # # # ****************************************************************************/ # # # ################################################################################### &process_pack; # Take anything in %pack, and fix %pack_vars and %pack_string } sub unpack_hash { my($key,$gibberish) = @_; my($p) = $pack_string{$key}; my($v) = $pack_vars{$key}; my(@v) = split(/\s+/o,$v); my(@w) = unpack($p,$gibberish); $debug && print "\nunpacking a $key\n"; $debug && print "using $p to unpack, we get " . scalar @v . " names for " . scalar @w . " values\n"; my(%hash)=(); while(@w) { my($w) = shift @w; $v=shift @v; $debug && print "assigning $v=$w\n" unless ($v =~ m/(unknown_|filler)/) ; $hash{$v}=$w; } return %hash; } sub pack_hash { my($key,%hash) = @_; my($p)=$pack_string{$key}; $p =~ s#\bA#a#g; my(@p) = split(/\s+/o,$p); my($v)=$pack_vars{$key}; my(@v)=split(/\s+/o,$v); my(@w) = (); foreach (@v) { my $testp = shift @p; my $value = $hash{$_}; unless (defined $value) { if ($testp =~ m/^[au]/i) { $value = ""; } elsif ($testp =~ m/^[fd]/i) { $value = "NaN"; } else { $value = 0; } } push(@w,$value); } my($gibberish) = pack($p,@w); return $gibberish; } sub process_pack { my($key); foreach $key (sort keys %pack) { my $template = $pack{$key}; $debug && print "PROCESSING TEMPLATE FOR $key\n"; my @lines = split(/\n/,$template); my $counter=0; my @pack = (); my @var = (); foreach (@lines) { s/^\s+//; # remove whitespace next if (/^#/); # comment next if (/^$/); # empty line if (/^(\S+)\s+#\s*(\S+)/) { push(@pack,$1); push(@var,$2); next; } if (/^(\S+)\s*$/) { push(@pack,$1); push(@var,"$counter"); $counter++; next; } $debug && print "Undefined line: $_\n"; } $pack_string{$key}=join(" ",@pack); $pack_vars{$key} =join(" ",@var); my($t) = pack($pack_string{$key}); $pack_eats{$key} = length($t); $debug && print "structure $key eats $pack_eats{$key} bytes using $pack_string{$key}\n"; } } sub enumhash { my ($counter) = 0; my(%hash); my($var) = shift @_; my($invvar) = "inv_$var"; my($val); while(@_) { $val = shift @_; ${$var}{$val}=$counter; ${$invvar}{$counter}=$val; $counter++; } while ($counter<10) { $val = "unknown_${var}_$counter"; ${$var}{$val}=$counter; ${$invvar}{$counter}=$val; $counter++; } } sub showhelp { my ($argv0) = $0; $argv0 =~ s#.*/##g; print <