samithawijedasa has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I have cross compiled perl-5.18.2 and integrated it in one of the embedded platforms, but currently it occupies close to 15Mb disk space. The only usage of perl in my system is to run the ipac-ng's ipacsum which is a perl script used to aggregate and create reports from iptable counters. I want to disable unnecessary perl modules so i can reduce the size of the installation. I would like to know what are the unnecessary modules. The perl script is as follows

#!/usr/bin/perl # # $Id: ipacsum,v 1.23 2011/09/25 23:21:09 smsoft Exp $ # # Summarize all IP accounting files from start to end time # # Copyright (C) 1997 - 2000 Moritz Both # 2001 - 2002 Al Zaharov # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # The author can be reached via email: moritz@daneben.de, or by # snail mail: Moritz Both, Im Moore 26, 30167 Hannover, # Germany. Phone: +49-511-1610129 # use 5.000; use Getopt::Long; use Sys::Hostname; use POSIX qw(strftime); use Time::Local; use Socket; use IO::Handle; BEGIN { eval {require GD; import GD;}; $have_GD = $@ ? 0 : 1; } @moff = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ); @mofg = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31); @mons = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); @wday_short = ("Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"); @wday = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); # =()<$datdir="@<ACCTDIR>@";>()= $datdir="/var/lib/ipac"; # =()<$datdelim="@<DATDELIM>@";>()= $datdelim="#-#-#-#-#"; # =()<$version="@<VERSION>@";>()= $version="1.34.2"; # =()<$prefix="@<prefix>@";>()= $prefix="/usr/local"; # =()<$exec_prefix="@<exec_prefix>@";>()= $exec_prefix="${prefix}"; # =()<$INSTALLPATH="@<INSTALLPATH>@";>()= $INSTALLPATH="${exec_prefix}/sbin"; $copyright="(C) 1997 - 2000 Moritz Both\n(C) 2001 - 2003 Al Zaharov"; $me=$0; $me =~ s|^.*/([^/]+)$|$1|; $now = time; $fetchipac="$INSTALLPATH/fetchipac"; $replace = 0; $exact = 0; $progression = 0; # if 1, collect progressions (needs memory!) $graph = 0; # make ascii graphs $png = undef; # make png graphs $png_filename_prefix = undef; # prefix png files with this prefix $rule_regex = ".*"; # match rules with this regex only $show_run_progression = 0; # show % finished while running $fixed_quantity = undef; # undef==off; can be one of [tgmk ] $storage_method = undef; # the storage method to pass to fetchipac $c_day = 24*60*60; $c_year = $c_day * 365.24; @C_QU = (1, 1024, 1048576, 1073741824, 1099511627776 ); @C_QU_H = (1, 1000, 1_000_000, 1_000_000_000, 1_000_000_000_000 ); @C_QUC= ('','K', 'M', 'G', 'T'); # a list of possible x axis separator intervals and -alignments. @possible_interval = ( 1, 2, 5, 10, 15, 20, 30, # sec 60, 120, 300, 600, 900, 1200, 1800, # 1,2,5,10,15,20,30 min 3600, 7200, 10800, 14400, # 1,2,3,4 hours 21600, 43200, # 6,12 hours $c_day, $c_day*2, $c_day*5, $c_day*7, $c_day*15, $c_day*30, $c_day*60, $c_year/4, $c_year/3, $c_year/2, $c_year, $c_year*2, $c_year*4, $c_year*5, $c_year*10, $c_year*20, $c_year*25, $c_year*50, $c_year*100); # hope this is enough # a list of possible time labels on x axis of png image with reasonabl +e # time limits. # if duration is under... display time as... @png_time_labels = ( 4*60, 'sprintf("%02d\'%02d\'\'", $m, $s)', 60*60, 'sprintf("%02d", $m)', $c_day*2, 'sprintf("%02d:%02d", $h, $m)', 5*$c_day, 'sprintf("%s %02d", $WD, $h)', 50*$c_day, 'sprintf("%02d.", $D)', 2*$c_year, 'sprintf("%02d/%02d", $M, $D)', 1E37, 'sprintf("%04d", $Y)', ); $graph_width = 55; $graph_interval = 60*60; # seconds $graph_interval_explicit = 0; # 1 if the user set it # png defaults. # =()<$png_width=@<PNG_WIDTH>@;>()= $png_width=500; # =()<$png_height=@<PNG_HEIGHT>@;>()= $png_height=150; # =()<$png_index_default_name = "@<PNG_INDEX_DEFAULT>@";>()= $png_index_default_name = "index.html"; # png defaults. $png_x_sp = 50; $png_x_spr = 3; # space on right side of png picture $png_y_sp = 15; $png_xaxis_sep_per_pix = 0.015; # seperators on x axis per pixel $png_yaxis_sep_per_pix = 0.04; # separators on y axis per pixel $png_yaxix_sep_width = 2; $png_xaxix_sep_height = 2; $png_font = GD::gdSmallFont; $png_font_offset_xax_x = -3; $png_font_offset_xax_y = 1; $png_font_offset_yax_x = -7; $png_font_offset_yax_y = -8; $png_average_character_width = 6; $png_average_character_width_vert = 4; $png_top_label_height = 15; $png_index = "0"; # wheather to generate index.html $png_normalized = 1; # normalize png: show bps (not b per # graph_interval) $png_caption_in_index = 0; # make max: and Average: in html index f +ile $png_sort_by_bytes = 0; # no sorting by default $png_sort_by_pkts = 0; # no sorting by default $png_no_average = 0; # dont draw dotted horizontal line with av +erage $png_asis = 0; # create 'asis' files (apache) $png_average_curve = 0; # create 'average on n dots around' lin +e $png_total = 0; # show total in image $png_to_stdout = 0; # send images to stdout # png defaults for other fonts. %png_defaults_fonts = ( TINY => [ \$png_x_sp, 30, \$png_y_sp, 10, \$png_xaxis_sep_per_pix, 0.03, \$png_yaxis_sep_per_pix, 0.04, \$png_font_offset_yax_y, -3, \$png_font_offset_xax_y, 2, \$png_average_character_width, 3, \$png_top_label_height, 10, ] ); # calculate time zone offset in seconds - use difference of output of +date # command and time function, round it my $tzoffset = time-timegm(localtime()); # get time zone name $tzname = strftime "%Z", localtime; # get host name $hostname = &hostname; $starttime = 0 + $tzoffset; $endtime = $now; $starttime_explicit = 0; $endtime_explicit = 0; $machine_name = undef; # configure option parser if the Getopt::Long package is new enough to # support this. eval {&Getopt::Long::config("bundling_override")}; $getopt_supports_bundling = $@ ? 0 : 1; # find out if we use gif or png images. GD versions <=1.19 support GIF +, # >=1.20 make png images. if ($have_GD) { $png_not_gif = ($GD::VERSION ge "1.20"); if ($png_not_gif) { $image_type = "png"; $image_TYPE = "PNG"; } else { $image_type = "gif"; $image_TYPE = "GIF"; } } # parse command line. Option values are placed in $opt_X @GetOptionsControl= ( "d|dir=s", #@ DEBUG CODE BEGIN "debug=s" =>\$debug, "debug-current-time=s", #@ DEBUG CODE END "e|endtime=s", "fetchipac=s", =>\$fetchipac, "f|filter=s", "l|sfilter=s", "fixed-quantity=s" =>\$fixed_quantity, "H|hostname=s" =>\$hname, # for backward compatibility, allow options to be named 'gif-*'. "gif:s", "gif-filename-prefix=s"=>\$png_filename_prefix, "gif-asis", \$png_asis, "gif-average-curve=i" =>\$png_average_curve, "gif-no-average" => \$png_no_average, "gif-caption-in-index"=>\$png_caption_in_index, "gif-height=i" => \$png_height, "gif-index:s" => \$png_index, "gif-normalize=i"=> \$png_normalized, "gif-total" => \$png_total, "gif-use-smallfont", "gif-width=i" => \$png_width, "gif-to-stdout=i" => \$png_to_stdout, "g|graph", "h|help", "human-kilo", "z|omit-zero-lines", "i|interval|intervall=s", "png:s", "png-filename-prefix=s"=>\$png_filename_prefix, "png-asis", \$png_asis, "png-average-curve=i" =>\$png_average_curve, "png-no-average" => \$png_no_average, "png-caption-in-index"=>\$png_caption_in_index, "png-sort-by-bytes" => \$png_sort_by_bytes, "png-sort-by-pkts" => \$png_sort_by_pkts, "png-height=i" => \$png_height, "png-index:s" => \$png_index, "png-normalize=i"=> \$png_normalized, "png-total" => \$png_total, "png-use-smallfont", "png-width=i" => \$png_width, "png-to-stdout" => \$png_to_stdout, "r|replace", "s|starttime=s", "show-run-progression" => \$show_run_progression, "storage-method=s" => \$storage_method, "t|timeframe=s", "version", "save-memory", "x|exact", ); if (! &GetOptions(@GetOptionsControl)) { unless ($getopt_supports_bundling) { warn "(Use a space character between option letters and ". "their values. Or update to a\nnewer version of the ". "Getopt::Long perl module.)\n"; } die "$me: illegal option specified. \"$me --help\" for help.\n"; } if ($opt_version || $opt_version) { print <<EOF; ipacsum version $version EOF exit 0; } if ($opt_h || $opt_h) { &usage_png if (defined($opt_png) || defined($opt_gif)); &usage; } #@ DEBUG CODE BEGIN $now = &makeunixtime($opt_debug_current_time) if ($opt_debug_current_time); #@ DEBUG CODE END if ($opt_s) { $starttime_explicit=1; $starttime=makeunixtime($opt_s); $starttime = $now - &parse_cmd_time($opt_s) if (!$starttime); } if ($opt_e) { $endtime_explicit=1; $endtime=makeunixtime($opt_e); $endtime = $now - &parse_cmd_time($opt_e) if (!$endtime); $endtime -= 1; } $exact = 1 if ($opt_x || $opt_x); if ($opt_r || $opt_r) { if (! defined($hname)) { warn "Specifiying -r/--replace requires -H or --hostname.\n"; &usage; } $replace = 1; } $graph = 1 if ($opt_g || $opt_g); $opt_png = $opt_gif if (defined($opt_gif)); $png = $opt_png if (defined($opt_png)); $png = "." if (defined($png) && !$png); if ($png && !$have_GD) { die "$me: cant draw png images because perl GD library not found\n +"; } if ($opt_png_use_smallfont || $opt_gif_use_smallfont) { my($i); $png_font = gdTinyFont; for ($i=$[; $i <= $#{$png_defaults_fonts{TINY}}; $i+=2) { ${${$png_defaults_fonts{TINY}}[$i]} = ${$png_defaults_fonts{TINY}}[$i+1]; } } $progression = 1 if ($graph || $png); $rule_regex = $opt_f if ($opt_f); if ($opt_i) { die "$me: invalid option --interval without --graph or --png\n" if (! $graph && ! $png); $graph_interval=parse_cmd_time($opt_i); $graph_interval_explicit = 1; } if ($opt_t) { $starttime_explicit=$endtime_explicit=1; &set_time_frame($opt_t); } if ($opt_human_kilo) { @C_QU = @C_QU_H; } $datdir = $opt_d if ($opt_d); # fixed quantity? if (defined($fixed_quantity)) { my $n; $fixed_quantity =~ tr/a-z/A-Z/; $fixed_quantity =~ s/^(.)/$1/; for ($n=0; $n<=$#C_QUC; $n++) { if ($fixed_quantity eq $C_QUC[$n]) { $fixed_quantity = $n; last; } } if ($n > $#C_QUC) { die "$me: option --fixed-quantity requires an argument '', K, +M, G or T\n"; } } # options that we need to pass to fetchipac if we call it. $fetchipac_options = "--directory=$datdir"; $fetchipac_options .= " --storage-method=$storage_method" if (defined($storage_method)); if ($hname || $hname) { $hostname = $hname; $fetchipac_options .= " -H $hostname"; } $fetchipac_options .= " -f \"$opt_l\"" if ($opt_l || $opt_l); $endtime = $now if ($endtime > $now); $starttime = 0 if ($starttime < 0); $mystarttime = makemytime($starttime); $myendtime = makemytime($endtime); %rule_firstfile = %rule_lastfile = ( ); if ($png_average_curve) { # initialize array of multipliers (weights) @av_curv_weight = ( ); $av_curv_weight_sum = 0; my($half) = $png_average_curve / 2; for ($i=0; $i<=$png_average_curve; $i++) { $av_curv_weight[$i] = (1-abs($i-$half)/$half)*2; $av_curv_weight_sum += $av_curv_weight[$i]; } } # find out which timestamps we need to read. # remember newest timestamp before starttime so we know when data for # the first file starts # also remember oldest timestamp after end time $newest_timestamp_before_starttime = ""; $oldest_timestamp_after_endtime = ""; open(DATA, "$fetchipac $fetchipac_options --timestamps=$starttime,$end +time ". "--machine-output-format|") || die "$me: cant run $fetchipac\n +"; # the first thing is the timestamp count $count=<DATA>; if ($count == 0) { die "$me: no data found in given timeframe\n" if ($replace == 0); exit 0; } while(<DATA>) { if (/^(.)\s(\d+)$/) { my $ts = $2; if ($1 eq "-") { $newest_timestamp_before_starttime=$ts; } elsif ($1 eq "+") { $oldest_timestamp_after_endtime=$ts; } elsif ($1 eq "*") { push(@timestamps, $ts); } else { die "$me: illegal output from $fetchipac: \"$_\"\n"; } } else { die "$me: illegal output from $fetchipac: \"$_\"\n"; } } close DATA; push(@timestamps, $oldest_timestamp_after_endtime) if ($oldest_timestamp_after_endtime); unshift(@timestamps, $newest_timestamp_before_starttime) if ($newest_timestamp_before_starttime); $rulenumber = 0; #@ DEBUG CODE BEGIN if ($debug =~ /timestamps_to_read/) { print "DEBUG: timestamps to read="; foreach (@timestamps) { print "$_, "; } print "\n"; } #@ DEBUG CODE END # read all data we need and put the data into memory. &read_data; @rules_presorted = sort keys %rulenames; @rules_presorted = sort { $bytes{$b} <=> $bytes{$a} } keys %rulenames +if $png_sort_by_bytes; @rules_presorted = sort { $pkts{$b} <=> $pkts{$a} } keys %rulenames if + $png_sort_by_pkts; foreach (@rules_presorted) { if (/$rule_regex/ && !($opt_z && $bytes{$_} eq "0")) { push @rules_sorted, $_; } } &make_one_record_from_many() if $replace; if (!$png_to_stdout) { printf "IP accounting summary\nHost: $hostname / Time created: %s +$tzname\n", nice_date(makemytime($now)); printf "Data from %s $tzname to %s $tzname\n", nice_date($starttime_explicit || !@timestamps ? $mystarttime : makemytime($timestamps[$[ +])), nice_date($myendtime); ## $incomplete_data=0; foreach (@rules_sorted) { &print_sum_line($_); } ## if ($incomplete_data) { ## print "* = data incomplete, rule was not there all the time\ +n"; ## } } if ($graph || $png) { &out_graph($png); } ########################## # END OF MAIN PROGRAM ########################## sub print_sum_line { my($f) = shift; my($s) = " "; ## if ($glb_number_of_records > $rule_count{$f}) { ## $incomplete_data++; ## $s="*"; ## } printf("%s %s: %15s\n", $s, $filter{$f}, &customized_number($bytes{$f}) ); } # read all data (@timestmaps contains the timestamps, must be sorted!) # and put the data into our global memory data # structures. special care must be taken with data of the first and # the last timestamps we read, since we only want data which is from o +ur # time frame. Furthermore, data from before and after this time frame # must be preserved in special data structures because we might replac +e # them (option --replace) and have to write extra data for these times # then. sub read_data { my $run_s; my $s; my $i; if ($show_run_progression) { $| = 1; $run_s = ""; printf " (%d records total)\r", $#timestamps+1; } my $in_time = 0; my $after_time = 0; # global: $glb_number_of_records = 0; # feed the timestamp list to fetchipac on its stdin. socketpair(CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die "socketpair: $!"; CHILD->autoflush(1); PARENT->autoflush(1); my $pid = open(CHILD, "-|"); die "$me: can't fork: $!\n" unless defined $pid; if ($pid == 0) { # child close CHILD; open(FETCHIPAC, "|$fetchipac $fetchipac_options --record " ."--machine-output-format") or die "$me: cant exec fetchipac\n"; #this is much more efficient than the original code (Manfred Weihs) # and it adds more troubles than solves (Al Zakharov) if ($timestamps[0] == $newest_timestamp_before_startti +me) { print(FETCHIPAC $timestamps[1],"-",$timestamps +[$count],"\n"); } else { print(FETCHIPAC $timestamps[0],"-",$timestamps +[$count-1],"\n"); } close(FETCHIPAC); close(PARENT); exit; } close PARENT; #@ DEBUG CODE BEGIN print("DEBUG: forked, this is parent\n") if ($debug =~ /fetchipac_call/); #@ DEBUG CODE END my $laststamp = undef; $laststamp = $newest_timestamp_before_starttime if ($newest_timestamp_before_starttime); $i = 0; $i++ if ($laststamp); while (<CHILD>) { #@ DEBUG CODE BEGIN print("DEBUG: read from fetchipac: \"$_\"\n") if ($debug =~ /fetchipac_call/); #@ DEBUG CODE END if ($show_run_progression) { my($s) = sprintf "%3d %%\r", int($i * 100 / ($#timestamps + 1)); if ($s != $run_s) { print($s); $run_s = $s; } } # first line of fetchipac output: "ADD" /^ADD\s*$/i or die "$me: bad line from fetchipac: $_\n"; # second line of fetchipac output: timestamp no_of_records $_ = <CHILD> || last; /^(\d+)\s(\d+)$/ or die "$me: bad line from fetchipac: $_\n"; my $timestamp = int $1; my $number_of_records = int $2; my $do_collect = 1; if ($timestamp < $starttime) { # this record is too old, we dont need the data. # However, the timestamp gives us a clue on the # time period the next item covers. $do_collect = 0; } my $irec; # read each record my $data = &read_data_record(CHILD, $number_of_records); if ($do_collect && $in_time == 0) { # the data is from after starttime. if it is the # first one, split the data (if we know for how # long this data is valid, and if $laststamp is not # equal to $starttime in which case the split is # redundant). If we don't have a clue about the # last file time before our first file was created, # we do not know how much of the file data is in our # time frame. we assume everything belongs to us. $in_time = 1; # if ($laststamp && $laststamp != $starttime) { if ($laststamp && $laststamp != $newest_timestamp_before_s +tarttime) { my $newdata = &split_data($data, $laststamp, $timestamp, $starttime); $glb_data_before = $data; $data = $newdata; $laststamp = $starttime; } } if ($timestamp > $endtime) { # this data is too new, but the data in it may have # begun within our time frame. (if endtime eq laststamp # we do a redundant split here, too - it works for now # and --replace relies on it, but it is ugly.) if ($after_time == 0) { $after_time = 1; if ($laststamp) { $glb_data_after = &split_data($data, $laststamp,$timestamp,$endtime); } else { $do_collect = 0; } } else { $do_collect = 0; # just too new. } } if ($do_collect) { &collect_data($data, $i); $glb_number_of_records++; } $laststamp = $timestamp; $i++; } print " \r" if ($show_run_progression); close CHILD; wait; } # split the data in $1 (format as from read_data) into a pair of two # such data sets. The set referenced to as $1 will afterwards contain # the first part of the data, another set which is returned contains # the second part of the data. # interpret the data as having start time=$2 and end time=$3 and split # time=$4 sub split_data { my $data = shift; my $mstart = shift; my $mend = shift; my $msplit = shift; # calculate factors for multiplications my $ust = $mstart; my $uperiod = $mend - $ust; my $usplit = $msplit - $ust; if ($uperiod < 0) { # hmmm? die Daten sind rueckwaerts??? $uperiod = -$uperiod; } my $fac1; if ($usplit < 0) { $fac1 = 0; } elsif ($usplit > $uperiod) { $fac1 = 1; } else { $fac1 = $usplit / $uperiod; } # $fac1 now says us how much weight the first result has. # initialize the set we will return. my @ret = ( ); foreach $set (@$data) { my ($rule, $bytes, $pkts) = @$set; $$set[1] = int($bytes * $fac1 + 0.5); $$set[2] = int($pkts * $fac1 + 0.5); push(@ret, [ $rule, $bytes - $$set[1], $pkts - $$set[2] ]); } return \@ret; } # put data from one file into global data structures # must be called in correct sorted file name order to set rules_lastfi +le # and rules_firstfile (which are currently useless) # arguments: # $1=index number of file; $2 = reference to array with data from file sub collect_data { my($filedata, $ifile, $i); $filedata = shift; $ifile=shift; for ($i=0; $i<=$#$filedata; $i++) { my $set = $$filedata[$i]; my $rule = $$set[0]; my $bytes = $$set[1]; my $pkts = $$set[2]; # if rule first appeared in this file, initialize its # life. if (!defined($rulenames{$rule})) { $rulenames{$rule}=$rulenumber++; &init_filter_id($rule); } $bytes{$rule} += $bytes; $pkts{$rule} += $pkts; $rule_count{$rule}++; ## $rule_lastfile{$rule} = $file; if ($progression) { $$prog_bytes{$rule}[$ifile] += $bytes; $$prog_pkts{$rule}[$ifile] += $pkts; $prog_bytes_max{$rule} = $bytes if ($prog_bytes_max{$rule} < $bytes); $prog_pkts_max{$rule} = $pkts if ($prog_pkts_max{$rule} < $pkts); } } } # initialize data variables for a new rule - if it is new sub init_filter_id { my($s, $ifile) = @_; if (!defined $bytes{$s}) { $bytes{$s}=0; $pkts{$s}=0; $filter{$s} = sprintf("%-48s", $s); $rule_firstfile{$s} = $timestamps[$ifile]; $rule_lastfile{$s} = ""; ## $rule_count{$s} = 0; if ($progression) { $prog_pkts{$s} = [ ]; $prog_pkts_max{$s} = 0; $prog_bytes{$s} = [ ]; $prog_bytes_max{$s} = 0; } } } # read data record from filehandle $1 # number of records is $2 # Return value: reference to array a of length n; # n is the number of rules # each field in a is an array aa with 3 fields # the fields in arrays aa are: [0]=name of rule; [1]=byte count; # [2]=packet count # function does not use global variables sub read_data_record { my($file, $number_of_records, $beforedata, $indata, $i, $irec); my($pkts, $bytes, $rule); my(@result); $file=shift; $number_of_records = shift; $indata=0; $beforedata=1; for($irec = 0; $irec < $number_of_records; $irec++) { $_ = <$file>; chop; #@ DEBUG CODE BEGIN print("DEBUG: read data from fetchipac: \"$_\" LF\n") if ($debug =~ /fetchipac_call/); #@ DEBUG CODE END /^\(\s*(.*)$/ or die "$me: bad line from fetchipac (expecting +machine name): $_\n"; $machine_name = $1; # remember final machine name while(<$file>) { #@ DEBUG CODE BEGIN print("DEBUG: read data from fetchipac: \"$_\"\n") if ($debug =~ /fetchipac_call/); #@ DEBUG CODE END last if (/^\)$/); # terminating line ')' /^(\d+)\s(\d+)\s\|(.*)\|$/ or die "$me: bad line from fetchipac (expecting rule i +tem): $_\n"; $bytes = $1; $pkts = $2; $rule = $3; if ($rule =~ /$rule_regex/) { push(@result, [ $rule, $bytes, $pkts]); } } } # read another emtpy line (data format consistency) $_ = <$file>; die "$me: bad data from fetchipac (expected emtpy line): $_\n" if ($_ !~ /^$/); \@result; } # given a string in format YYYYMMDD[hh[mm[ss]]], make unix time # use time zone offset $tzoffset (input=wall clock time, output=UTC) sub makeunixtime { my($y, $m, $d, $h, $i, $e); $s = shift; $h=0; $i=0; $e=0; if ($s =~ /^(\d\d\d\d)(\d\d)(\d\d)/) { ($y, $m, $d) = ($1, $2, $3); if ($s =~ /^\d\d\d\d\d\d\d\d-?(\d\d)/) { $h=$1; if ($s =~ /^\d\d\d\d\d\d\d\d-?\d\d(\d\d)/) { $i=$1; if ($s =~ /^\d\d\d\d\d\d\d\d-?\d\d\d\d(\d\d)/) { $e=$1; } } } } else { return 0; } $y-=1970; $s = (($y)*365) + int(($y+2)/4) + $moff[$m-1] + $d-1; $s-- if (($y+2)%4 == 0 && $m < 3); $s*86400 + $h*3600 + $i*60 + $e + $tzoffset; } # return the given unix time in localtime in "my" time format sub makemytime { my($s)=shift; my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($s); # ugly side effect of this function: set a global scalar # containing the day of week number. $mmt_wday = $wday; return sprintf("%04d%02d%02d-%02d%02d%02d", 1900+$year, $mon+1, $m +day, $hour, $min, $sec); } # parse time as a duration # syntax is # cmd_time: seconds | cmd_time_with_size # cmd_time_with_size: cmd_time_atom | cmd_time_with_size cmd_time_a +tom # cmd_time_atom: number size # size: "s"|"m"|"h"|"D"|"W"|"M"|"Y" # (sec, min, hours, Days, Weeks, Months, Years) # seconds: number # return number of seconds sub parse_cmd_time { my($sec) =0; $_=shift; return $_ if (/^\d+$/); while($_) { if (! /^(\d+)\s?([smhDWMY])(.*)$/) { die "$me: syntax error in time (duration)\n"; } $_=$3; if ($2 eq "s") { $sec += $1; } elsif ($2 eq "m") { $sec += $1*60; } elsif ($2 eq "h") { $sec += $1*60*60; } elsif ($2 eq "D") { $sec += $1*60*60*24; } elsif ($2 eq "W") { $sec += $1*60*60*24*7; } elsif ($2 eq "M") { $sec += $1*60*60*24*30; } elsif ($2 eq "Y") { $sec += $1*60*60*24*365; } } $sec; } sub set_time_frame { my($opt_t) = shift; my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($now); if ($opt_t =~ /^this\s*hour/i) { $opt_t = "the hour 0 hours ago"; } elsif ($opt_t =~ /^last\s*hour/i) { $opt_t = "the hour 1 hour ago"; } elsif ($opt_t =~ /^today/i) { $opt_t = "the day 0 days ago"; } elsif ($opt_t =~ /^yesterday/i) { $opt_t = "the day 1 day ago"; } elsif ($opt_t =~ /^the\s*day\s*before\s*yesterday/i) { $opt_t = "the day 2 days ago"; } elsif ($opt_t =~ /^this\s*week/i) { $opt_t = "the week 0 weeks ago"; } elsif ($opt_t =~ /^last\s*week/i) { $opt_t = "the week 1 week ago"; } elsif ($opt_t =~ /^the\s*week\s*before\s*last\s*week/i) { $opt_t = "the week 2 weeks ago"; } elsif ($opt_t =~ /^this\s*month/i) { $opt_t = "the month 0 months ago"; } elsif ($opt_t =~ /^last\s*month/i) { $opt_t = "the month 1 month ago"; } elsif ($opt_t =~ /^this\s*year/i) { $opt_t = "the year 0 years ago"; } elsif ($opt_t =~ /^last\s*year/i) { $opt_t = "the year 1 year ago"; } if ($opt_t =~ /^the hour (\d+) hours? ago/i) { $i=$1; my($thishour)=makeunixtime(sprintf("%04d%02d%02d%02d0000", 1900+$year, $mon+1, $mday, $hour)); $starttime=$thishour - 60*60 * $i; $endtime = $thishour - 60*60 * ($i-1); } elsif ($opt_t =~ /^the day (\d+) days? ago/i) { $i=$1; my($thismorning)=makeunixtime(sprintf("%04d%02d%02d000000", 1900+$year, $mon+1, $mday)); $starttime=$thismorning - 60*60*24 * $i; $endtime=$thismorning - 60*60*24 * ($i-1); } elsif ($opt_t =~ /^the week (\d+) weeks? ago/i) { $i=$1; $mday = $mday-($wday >0 ? $wday-1 : 6); if ($mday < 1) { $mon--; if ($mon < 0) { $mon += 12; $year--; } $mday += $mofg[$mon]; } my($monday)=makeunixtime(sprintf("%04d%02d%02d000000", 1900+$year, $mon+1, $mday)); $starttime=$monday - 60*60*24*7 * $i; $endtime=$monday- 60*60*24*7 * ($i-1); } elsif ($opt_t =~ /^the month (\d+) months? ago/i) { $mon = $mon - $1; while ($mon < 0) { $year--; $mon += 12; } $starttime=makeunixtime(sprintf("%04d%02d01000000", 1900+$year, $mon+1)); $endtime=$starttime + 60*60*24*$mofg[$mon]; $endtime += 60*60*24 if ((1900+$year)%4 ==0 && $mon==1); } elsif ($opt_t =~ /^the year (\d+) years? ago/i) { $i=$1; $starttime=makeunixtime(sprintf("%04d0101000000", 1900+$year-$i)); $endtime=makeunixtime(sprintf("%04d0101000000", 1900+$year-$i+1)); } elsif ((!$opt_t =~ /^ever/i) || ($opt_t ne "")) { die "$me: Unknown time frame: \"$opt_t\"\n"; } $endtime -= 1; } sub nice_number { my($n)=shift; $done=0; for ($i=$#C_QU; $i>0 && !$done; $i--) { if ($n >= $C_QU[$i]) { $n = sprintf("%d%s", int($n/$C_QU[$i]+.5), $C_QUC[$i]); $done = 1; } } $n; } sub customized_number { my $n = shift; if ($exact) { } elsif (defined($fixed_quantity)) { $n = int($n/$C_QU[$fixed_quantity]+.5); $n .= $C_QUC[$fixed_quantity] if ($C_QUC[$fixed_quantity]); } else { $n = &nice_number($n); } $n; } # format date in format YYYYMMDD-HHMMSS nicely. sub nice_date { $s = shift; my($wday); $s =~ s@^(\d\d\d\d)(\d\d)(\d\d)-(\d\d)(\d\d)(\d\d)@$1/$2/$3 $4:$5: +$6@; $s; } # Format a number representing seconds into a nice time (duration). sub nice_time { my($s) = shift; my($t, $i, $mz); $t = ""; $i = int($s / 31557600); $s = $s % 31557600; $mz = "s" if ($i > 1); $t = "$i year$mz " if ($i); $i = int($s / 86400); $s = $s % 86400; $mz = "s" if ($i > 1); $t = "$t$i day$mz " if ($i); $i = int($s / 3600); $s = $s % 3600; $mz = "s" if ($i > 1); $t = "$t$i hour$mz " if ($i); $i = int($s / 60); $s = $s % 60; $t = "$t$i min " if ($i); $t = "$t$s sec" if ($s); $t; } # replace: summarize all data from within time frame into one record # generate extra records for times before and after the time frame # if we have such data # try to be atomic and clean up in case of failures. # collect commands for fetchipac in an array sub make_one_record_from_many { my @commands = (); return if (! @timestamps); # collect timestamps to be unlinked. Unlink all but # $newest_timestamp_before_starttime and # $oldest_timestamp_after_endtime my $ok = 1; my($timestamp1, $timestamp2, $timestamp3); foreach(@timestamps) { next if ($_ eq $newest_timestamp_before_starttime); next if ($_ eq $oldest_timestamp_after_endtime); push(@commands, "DELETE $_\n"); } if ($ok && $#commands < $[) { # no records to summarize. return; } # the new timestamp number is equal to end time. if ($ok) { if ($starttime_explicit) { $timestamp1 = $starttime; } else { $timestamp1 = $timestamps[0]; } if ($timestamp1 eq $newewst_timestamp_before_starttime) { $timestamp1 = $timestamps[1]; } my $data = &sum_file; my $record_data = &ascii_data_record($data); push(@commands, "ADD\n$timestamp1 1\n" . $record_data . "\n"); } # write two more records: # if ($ok) { # # 1) before starttime, if $glb_data_before # if (defined($glb_data_before)) { # $timestamp2 = $starttime; # $timestamp2 = $newewst_timestamp_before_starttime; # my $record_data = &ascii_data_record($glb_data_before); # push(@commands, "ADD\n$timestamp2 1\n$record_data\n"); # # "(pre-timeframe split data part)"); # } # } # if ($ok) { # # 2) after endtime, if $glb_data_after # if (defined($glb_data_after)) { # $timestamp3 = $oldest_timestamp_after_endtime; # my $record_data = &ascii_data_record($glb_data_after); # push(@commands, "ADD\n$timestamp3 1\n$record_data\n"); # # "(post-timeframe split data part)"); # } # } # execute everything, scanning for errors. # if ($debug =~ /replace_actions/) { print @commands; # return; # } pipe(PREAD, PWRITE); my $pid = fork(); die "$me: can't fork: $!\n" if (!defined($pid)); if ($pid == 0) { # child close PWRITE; close STDIN; open(STDIN, "<&PREAD") || die "$me: cant redirect stdin to pip +e: $!\n"; open(FETCHIPAC, "$fetchipac $fetchipac_options --batch|") || die "$me: cant run fetchipac in batch mode: $!\n"; my @out; my $ret = 0; while(<FETCHIPAC>) { push(@out, $_); if (/ERROR/) { $ret = 1; last; } } close FETCHIPAC; close PREAD; exit $ret; } # parent close PREAD; foreach(@commands) { print(PWRITE $_); } close PWRITE; wait; die "$me: fetchipac reported error on replace operation\n" if (($? >> 8 ) != 0); } # from the global data, generate one record of data in the format of # read_file sub sum_file { my $rule; my @result = ( ); # sort rules by rule number (sequence of appearance) foreach $rule (sort { $rulenames{$a} <=> $rulenames{$b} } keys %rulenames) { push(@result, [ $rule, $bytes{$rule}, $pkts{$rule} ]); } return \@result; } ## create a data record in ascii format # @param $1 the data # @return the data record string or undef in case of error sub ascii_data_record { my $data = shift; # we "print" the record in memory my($text); # push(@text, sprintf("# ipac $version summary file generated %s\n +", makemytime(time))); # push(@text, sprintf("# source files: %s to %s\n", $files[0], # $files[$#files])); # push(@text, "# $comment\n") if (defined($comment)); # push(@text, "#\n"); # machine name $text .= "( $machine_name\n"; my $set; foreach $set (@$data) { $text .= "$$set[1] $$set[2] \|$$set[0]\|\n"; } $text .= ")\n"; # sub record delimiter return $text; } # find identical rules in $1 and make them one by adding the counters. sub compress_data_record { my $data = shift; my $rule; my $set; my @newdata; my $set2; foreach $set (@$data) { $rule = $$set[0]; next if (!defined($rule)); my $bytes = 0; my $pkts = 0; foreach $set2 (@$data) { if ($rule eq $$set2[0]) { $bytes += $$set2[1]; $pkts += $$set2[2]; $$set2[0] = undef; } } push(@newdata, [ $rule, $bytes, $pkts ]); } @$data = @newdata; } sub out_graph { my($png) = shift; my(@rules_filtered, @rules_filtered_data, $i, $lst); # Do preparations for all graphs. $startt_graph = $starttime; $startt_graph = $timestamps[$[] if (!$starttime_explicit && @timestamps); # IF we make a png and we show throughput instead of absolute valu +es # (which is the normal case), AND # the user has not set --interval, OR # $graph_inverval is smaller than one pixel would be # THEN # set $graph_interval to one pixel. $i = $png_width-$png_x_sp-$png_x_spr; # actual x pixel number if ($png && $png_normalized && ( ($endtime - $startt_graph)/$graph_interval > $i || ! $graph_interval_explicit) ) { $graph_interval = ($endtime-$startt_graph) / $i; } # Calculate HTTP Expires: Header. # if $endtime is $now, set it to $now + $graph_interval. # if it is not $now, omit it. undef($expires); if ($endtime == $now) { $expires = &http_time($now + $graph_interval); } # HTTP Last-Modified (asis) $last_modified = &http_time($now) if ($png_asis); $lst = ""; foreach $rule (@rules_sorted) { next if ($rule eq $lst); $lst = $rule; push(@rules_filtered, $rule); my($graph_data) = &single_graph($rule, $png); push(@rules_filtered_data, $graph_data); } &out_index_html(\@rules_filtered, \@rules_filtered_data) if ($png && ($png_index ne "0")); } sub single_graph { my($rule) = shift; my($png) = shift; my($max, $i, $ifile, $inter_st, $inter_end, $value, $iut, $oldiut) +; my($sum, $avg, @values, @valtime, $bytes, %dat); print "Graph for rule \"$rule\"\n" unless $png; $max=0; $sum = 0; $inter_st = $startt_graph; $inter_end = $inter_st + $graph_interval; $value=0; $iut=$inter_st; for ($ifile=0; $ifile<=$#timestamps; $ifile++) { if (defined($$prog_bytes{$rule}[$ifile])) { $bytes = $$prog_bytes{$rule}[$ifile]; } else { $bytes = 0; } # while the timestamp is before start time, skip next if ($timestamps[$ifile] <= $startt_graph); $oldiut = $iut; $iut = $timestamps[$ifile]; # if the file is younger than the end of the time frame # we are examining, split the count. while ($iut > $inter_end) { # if the timestamp is from after our overall end time, # pretend it is from end time. (Data has been adjusted # in read_data() anyway.) if ($iut > $endtime) { $iut = $endtime; } # number of bytes still in current time frame $i = ($bytes != 0 ? int($bytes*($inter_end-$oldiut) /($iut-$oldiut)+.5) : 0); $value += $i; push(@values, $value); $max = $value if ($value > $max); $sum += $value; $value=0; $oldiut=$inter_end; $inter_st = $inter_end; $inter_end += $graph_interval; $bytes -= $i; } $value += $bytes; } # no: the value will be inaccurate push(@values, $value); push(@values, $value); $max = $value if ($value > $max); $sum += $value; $avg = @values ? ($sum / ($#values + 1)) : 0; %dat = ( 'max' => $max, 'min' => 0, 'sum' => $sum, 'avg' => $avg ) +; if ($png) { my($i) = &draw_png($png_width, $png_height, \%dat, \@values, $startt_graph, $rule); my($filename)= $png_asis ? "$rule.asis" : "$rule.$image_type"; $filename = &good_filename($filename); if (-d $png) { if ($png_filename_prefix ne "") { $filename="$png/$png_filename_prefix$filename"; } else { $filename="$png/$filename"; } } if (!$png_to_stdout) { open(OUT, ">$filename") || die "$me: can't open \"$filenam +e\": $!\n"; # } else { # print "$filename\n"; } if ($png_asis) { if ($png_to_stdout) { print "Status: 200 OK\n"; print "Expires: $expires\n" if ($expires); print "Last-Modified: $last_modified\n"; printf "Content-Length: %d\n", length($i); print "Content-Type: image/$image_type\n"; print "\n"; } else { print OUT "Status: 200 OK\n"; print OUT "Expires: $expires\n" if ($expires); print OUT "Last-Modified: $last_modified\n"; printf OUT "Content-Length: %d\n", length($i); print OUT "Content-Type: image/$image_type\n"; print OUT "\n"; } } if (!$png_to_stdout) { binmode OUT; print OUT $i; close OUT; } else { print "$i\n"; } } else { &draw_ascii_graph(\@values, $max, $startt_graph); } # return some interesting data about the graph. return \%dat; } sub good_filename { my($s) = shift; $s =~ s@[\s\\/\|\>\<]@_@g; $s; } sub draw_ascii_graph { my($values, $max, $startt_graph) = @_; my($s, $i, $inter_st); printf "time bytes 0%s%5s\n", string(" ", $graph_width-6) +, nice_number($max); $inter_st=$startt_graph; for ($i=0; $i<=$#{$values}; $i++) { $s= $max ? string("*", int(($$values[$i]/$max)*$graph_width+.5) ) : ""; printf "%s %s\n", nice_date(makemytime($inter_st)), $s; $inter_st += $graph_interval; } } # repeat string n times sub string { my($s, $i); $s=""; for ($i=1; $i<$_[1]; $i++) { $s .= $_[0]; } $s; } sub draw_png { return 0 unless $have_GD; my($xsiz, $ysiz, $dat, $values, $startt_graph, $rule) = @_; my($maxval) = $$dat{'max'}; my($minval) = $$dat{'min'}; my($im) = new GD::Image($xsiz, $ysiz); # hack to leave pixels blank at the right border. $xsiz -= $png_x_spr; my($backg) = $im->colorAllocate(224,224,224); my($white) = $im->colorAllocate(255,255,255); my($black) = $im->colorAllocate(0,0,0); my($blue) = $im->colorAllocate(0,0,255); my($green) = $im->colorAllocate(0,128,0); my($red) = $im->colorAllocate(255,0,0); my($png_arc_size)=1; my($x, $y, $i, $s, $xstep, $ymulti, $arc_color, $line_color, $font_color, $title_color, $axle_color, $x1, $y1, $duration, $normalize_factor, $avg_color); $im -> interlaced(1); $maxval = 1 unless $maxval; $png_arc_size = 5 unless $png_normalized; # normalize_factor is a multiplier for Y values to get user # caption values. $normalize_factor = $png_normalized?$png_normalized/$graph_interva +l : 1; $$dat{'max_caption'} = $maxval * $normalize_factor; $$dat{'avg_caption'} = $$dat{'avg'} * $normalize_factor; # division by 0 in the following line? FIXME $xstep = ($xsiz-$png_x_sp) / ($#{$values} + 1); $ymulti = ($ysiz-$png_y_sp-$png_top_label_height) / ($maxval-$minv +al); $arc_color = $black; $line_color = $black; $font_color = $blue; $axle_color = $blue; $title_color = $green; $avg_color = $green; $avg_line_color = $red; # draw axles. $im->line($png_x_sp, $ysiz-$png_y_sp, $xsiz, $ysiz-$png_y_sp, $axle_color); $im->line($png_x_sp, $png_top_label_height, $png_x_sp, $ysiz-$png_y_sp, $axle_color); $s = $png_normalized ? $png_normalized : $graph_interval; if ($s == 1) { $s = "bytes / sec"; } elsif ($s == 8) { $s = "bit / sec"; } else { $s = "bytes / $s sec"; } &centerStringUp($im, $s, $png_font, 1, $png_top_label_height, $ysiz, $font_color); # draw total average line if requested. unless ($png_no_average) { $y = ($ysiz - $png_y_sp) - $$dat{'avg'} * $ymulti; $im -> GD::Image::dashedLine($png_x_sp, $y, $xsiz, $y, $avg_color); } # compute optimal interval of labels. unless (defined($x_labels)) { $x_labels = &optimal_label_interval_time( int($png_xaxis_sep_per_pix * ($xsiz-$png_x_sp) + 0.5), $#{$values}+1, $startt_graph); } my($y_lab_int) = &optimal_label_interval_2( int($png_yaxis_sep_per_pix * ($ysiz-$png_y_sp-$png_top_label_height) + 0.5), $maxval*$normalize_factor, 1); # draw labels. # X AXIS $y = $ysiz - $png_y_sp; # time we cover in seconds $duration = ($#{$values}+1) * $graph_interval; # $%x_labels: keys are times, values are label strings foreach $i (keys %$x_labels) { $x = $png_x_sp + (($i - $startt_graph)/$duration * ($xsiz - $png_x_sp)); $im->line($x, $y - $png_xaxix_sep_height, $x, $y + $png_xaxix_sep_height, $axle_color); $im->string($png_font, $x + $png_font_offset_xax_x - length($$x_labels{$i}) / 2 * $png_average_character_width, $y + $png_font_offset_xax_y, $$x_labels{$i}, $font_color); } # Y AXIS. $x = $png_x_sp; for ($i = 0; $i <= $maxval * $normalize_factor; $i += $y_lab_int) +{ $y = ($ysiz-$png_y_sp) - int(($i / $normalize_factor * $ymulti + 0.5)); $im->line($x - $png_yaxix_sep_width, $y, $x + $png_yaxix_sep_width, $y, $axle_color); $s = &nice_number($i); $im->string($png_font, $png_x_sp - $png_average_character_width* length($s)+$png_font_offset_yax_x, $y + $png_font_offset_yax_y, $s, $font_color); } # put the first dot $i = $[; $x = $png_x_sp; $y = ($ysiz - $png_y_sp) - $$values[$i++] * $ymulti; $im -> GD::Image::arc($x, $y, $png_arc_size, $png_arc_size, 0, 360, $black) if ($png_arc_size >1); # put all lines and dots. $yavg = undef; my(@av_line); my($gal_offset) = int($png_average_curve/2+.5); while($i <= $#{$values}) { $x1 = $x + $xstep; if ($png_average_curve) { push(@av_line, $$values[$i]); shift(@av_line) if ($#av_line > $png_average_curve); if ($#av_line == $png_average_curve) { # draw it. my($ia); my($asum)=0; for ($ia=0; $ia<$png_average_curve+1; $ia++) { $asum += $av_line[$ia] * $av_curv_weight[$ia]; } $yavg1 = ($ysiz-$png_y_sp) -($asum/$av_curv_weight_sum) *$ymulti; if ($yavg) { $im-> GD::Image::line($x-$gal_offset, $yavg, $x1-$gal_offset, $yavg1, $avg_line_color); } $yavg = $yavg1; } } $y1 = ($ysiz - $png_y_sp) - $$values[$i++] * $ymulti; $im -> GD::Image::arc($x1, $y1, $png_arc_size, $png_arc_size, 0, 360, $arc_color) if ($png_arc_size >1); $im -> GD::Image::line($x, $y, $x1, $y1, $line_color); $x = $x1; $y = $y1; } # put the title string. $s = &nice_date(&makemytime($startt_graph)); # sets &mmt_wday $s=sprintf("%s Start: %s %s max: %s avg: %s", $rule, $wday[$mmt_wday], $s, &nice_number(int($$dat{'max_caption'}+0.5)), &nice_number(int($$dat{'avg_caption'}+0.5))); $s .= sprintf(" tot: %s", &nice_number($bytes{$rule})) if ($png_total); $x1 = length($s)*$png_average_character_width; $x = int($xsiz/2 - $x1/2+0.5); $im->string($png_font, $x, 0, $s, $title_color); return ($png_not_gif) ? $im->png : $im->gif; } sub optimal_label_interval { my($want_separators, $max, $useKM) = @_; my($mat, $exp); return 1 if (! $max || ! $want_separators); $mat = $max / $want_separators; $exp=0; # stupid way, i am not a mathematican. while ($mat >= 10) { $mat = $mat / 10; $exp++; } while ($mat < 1) { $mat = $mat * 10; $exp--; } # $mat is now between 1 and 9.99... if ($mat < 1.7) { $mat = 1; } elsif ($mat < 3.7) { $mat = 2.5; } elsif ($mat < 7.5) { $mat = 5; } else { $mat = 10; } $mat * (10 ** $exp); } # compute a list of time labels for the x axix. Make about $want_separ +ators # labels. First column is of time $startt_graph, there are $n values. sub optimal_label_interval_time { my($want_separators, $n, $startt_graph) = @_; my($duration, $sps, $div, $interval, $firsttime, $i, %result); my($Y, $M, $D, $h, $m, $s, $pfunc); # We need to find out what the best label notation and the best la +bel # interval is. The magic measure is "seconds per separator", sps. $duration = $n * $graph_interval; $sps = $duration / $want_separators; # find the closest value from @possible_interval. Linear search. for ($i=$[; $i<=$#possible_interval && $sps > $possible_interval[$ +i]; $i++) { } $i-- if ($i>$[ && $possible_interval[$i]-$sps > $sps-$possible_interval[$i-1]); $interval = $possible_interval[$i]; for ($i=$[; $i<=$#png_time_labels; $i+=2) { last if ($png_time_labels[$i] > $duration); } $pfunc = $png_time_labels[$i+1]; # this is the first time. $firsttime = int(($startt_graph + $interval - $tzoffset) / $interv +al - 0.001) * $interval + $tzoffset; # generate a list of labels. $n=""; for ($i = $firsttime; $i < $startt_graph+$duration; $i += $interva +l) { ($s,$m,$h,$D,$M,$Y,$WD) = localtime($i); $WD = $wday_short[$WD]; $M++; $Y+=1900; $result{$i} = eval($pfunc); if ($result{$i} eq $n) { # do not make two labels with the same value. $result{$i} = ""; } else { $n = $result{$i}; } } \%result; } sub optimal_label_interval_2 { my($want_separators, $max) = @_; my($mat, $x); return 1 if (! $max || ! $want_separators); $mat = int($max / $want_separators + 0.5); # find the power of 2 which is closest to $mat. for ($x = 1; $x < $mat; $x *= 2) {}; if ($x-$mat < $mat-($x/2)) { $mat = $x; } else { $mat = $x/2; } $mat ? $mat : 1; } sub out_index_html { my($rules) = shift; my($rules_data) = shift; my($filename); my($n_now, $n_st, $n_end, $s, $s1, $i); my($max, $avg, $tot, $resolution, $text); $n_st = nice_date($starttime_explicit || !@timestamps ? $mystartti +me : makemytime($timestamps[$[])); $n_end = nice_date($myendtime); $n_now = nice_date(makemytime($now)); $resolution = &nice_time($graph_interval); $filename = $png_index ? $png_index : $png_index_default_name; if (-d $png && $filename !~ m|^/|) { $filename="$png/$filename"; } $text = qq|<HTML> <HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1 +"> |; $text .= qq|<META HTTP-EQUIV="Expires" CONTENT="$expires">\n| if ($expires); $text .= qq|<TITLE>ip accounting graph page</TITLE> </HEAD> <BODY> <H1>ip accounting graph page</H1> <TABLE border=0 cellspacing=0> <TR> <TD>Host:</TD><TD>$hostname</TD> </TR> <TR> <TD>Time created:</TD><TD>$n_now $tzname</TD> </TR> <TR> <TD>Data Start time:</TD><TD>$n_st $tzname</TD> </TR> <TR> <TD>Data End time:</TD><TD>$n_end $tzname</TD> </TR> <TR> <TD>Resolution (time/pixel):</TD><TD>$resolution</TD> </TR> </TABLE> |; $text .= "<FONT SIZE=-1>"; foreach(@{$rules}) { $s = &good_filename($_); $text .= "[&nbsp;<A HREF=\"#$s\">$_</A>&nbsp;]&nbsp; "; } $text .= "</FONT>\n"; for ($i=0; $i<=$#{$rules}; $i++) { $_ = ${$rules}[$i]; $dat = ${$rules_data}[$i]; $s = &good_filename($_); $max = &nice_number(int($$dat{'max_caption'} + 0.5)); $avg = &nice_number(int($$dat{'avg_caption'} + 0.5)); $tot = &nice_number($bytes{$_}); $s1=""; $s1="<BR>Max: $max Average: $avg Total: $tot" if ($png_caption_in_index); $text .= qq|<A NAME="$s"><H3>$_</H3></A> <IMG ALT="graph for $_" border=0 SRC="|; if ($png_filename_prefix ne "") { $text .= "$png_filename_prefix"; } $text .= qq|$s.| . ($png_asis ? "asis":$image_type) . qq|" WIDTH=$png_width HEIGHT=$png_height> $s1 <HR> |; } $text .= qq|<ADDRESS> Generated by ipacsum which is part of IPAC-NG version $version. IPAC-N +G home page: <A HREF="http://sf.net/projects/ipac-ng"> http://sf.net/projects/ipac-ng</A> </ADDRESS> </BODY> </HTML> |; open(OUT, ">$filename") || die "$me: cant open \"$filename\": $!\n +"; print OUT $text; close OUT; } # Print a string upwards centered. sub centerStringUp { my($im, $text, $font, $x, $y1, $y2, $color) = @_; $y1 = $y1 + int(($y2-$y1)/2 + length($text)*$png_average_character_width_vert/2 + 0.5); $im->stringUp($font, $x, $y1, $text, $color); } sub http_time { my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(s +hift); # Fri, 27 Nov 1998 13:50:41 GMT sprintf "%s, %02d %s %04d %02d:%02d:%02d GMT", $wday[$wday], $mday, $mons[$mon], $year+1900, $hour, $min, $sec; } sub usage { print <<EOF; ipacsum V$version $copyright; see file COPYING for license Generates summary of ip accounting -- Usage: $me [Options] Options: --starttime t, -s t Start time, default: The epoch --endtime t, -e t End time, default: now Times are either absolute in format YYYYMMDD[hh[mm[ss]]] !Note year is + 4 digit! or relative in format n{s|m|h|D|W|M|Y}... (=sec, min, hours, Days, We +eks...) --fetchipac FILE override default name of fetchipac --filter r, -f r filter output by rule names on regular expression works after simple filter --sfilter r, -l r simple filter that works in storage backend (use it to reduce memory usage) --hostname h, -H h hostname to do report for, all hosts, if not s +pecified --omit-zero-lines, -z omit zero lines in summary --fixed-quantity Q show values in quantity Q; can be '', K, M, G o +r T --human-kilo use the human kilo 1K=1000 instead of 1K=1024 --graph, -g print ascii progression graph for every rule --interval n, -i n specify progression graph (-g) interval; defaul +t 1 hour; format: any combination of (number size) pairs, where size is one of smhDWMY (sec,min,hours,days,...) --timeframe t, -t t Start and End time in one; time_frame is one o +f today, yesterday, "the day before yesterday", "the day n days ago", "this week", "last week", "the week n weeks ago" and so on with (months, years) --help, -h Print this help --replace, -r replace all summarized accounting files by one. file name will be according to end time Note: this option requires the --hostname option! --dir d, -d d specify directory containing the accounting data --show-run-progression while running, show number of input files + pr +ogression $image_TYPE image creation options: Type $me --help --$image_type for +help. EOF exit 0; } sub usage_png { print <<EOF; ipacsum V$version $copyright; see file COPYING for license $image_TYPE image creation options: --$image_type [DIR] create $image_type images for each rule in + directory DIR (default DIR is the current directory) --$image_type-average-curve N draw extra line with "defocused" val +ues --$image_type-caption-in-index print statistical data in html index f +ile --$image_type-height N image height in pixels; default: $png_h +eight --$image_type-index [FILE] create HTML index file named FILE in $i +mage_type directory; default name: $png_index_default_name --$image_type-no-average do not draw dotted horizontal line for av +erage value --$image_type-normalize SEC set Y axis scale to bytes/SEC. Set to +0 to make scale absolute according to --interval setting; set to 8 for bit/sec. Default is 1 (bytes/sec). --$image_type-filename-prefix PREFIX prefix every $image_type imag +e with PREFIX --$image_type-total put total byte value into image caption --$image_type-use-smallfont use a smaller font in the image --$image_type-width N image width in pixels; default: $png_wid +th --$image_type-to-stdout output images to STDOUT instead of files EOF print <<EOF; NOTE: Image creation functions work only if you have the perl GD libra +ry EOF print "installed. This machine does "; if ($have_GD) { print "have it, so everything should work fine"; } else { print "not have it, so image creation is disabled"; } print ".\n"; if ($have_GD) { if (!$png_not_gif) { print <<EOF; NOTE: Usage of GIF images is depreciated due to copyright reasons (see ipacsum(8)). You might want to consider updating to GD library version + 1.20 or EOF print "newer for PNG images. " # no eol! } print <<EOF; You have GD library version $GD::VERSION, by the way. EOF } exit 0; } #@ DEBUG CODE BEGIN sub DBXXX { my $way = shift; if ($debug =~ /$way/) { print @_; } } #@ DEBUG CODE END # EOF

Replies are listed 'Best First'.
Re: disabling unnecessary perl modules
by Old_Gray_Bear (Bishop) on May 29, 2014 at 22:17 UTC
    So, which modules are included in your "cross-compile bundle"? (Hint, it will be documented in one of the build scripts.) Once you have that list of all the modules, determine which modules your code uses (Hint: you use modules) and cross them off the list. Look at each of these modules, determine their pre/co requisits and cross them off as well. Repeat this process. The remaining items on the list after all of the direct-, pre-, and co-requisites are subracted are candidates for removal.

    Start removing them, one at a time, and verify between each removal that the code still works properly (Hint: use your extensive test suit). If the code still works, congratualations, you have found a 'unnecessary' module. Repeat process for the rest of your list.

    Or, you could just buy a better hard-drive....

    Note: the 'unecessary modules' list you build here is good only for this particular version of your Code. The first time you try to add a feature, you may well discover that you need to add another module.

    You are creating a maintenance nightmare as a trade for a few megabytes of storage. In the age of multi-TByte disks and laptops with 500G of main memory, you are being "penny-wise and pound-foolish".

    Update: Corrected typo (Thanks MidLifeXis)

    ----
    I Go Back to Sleep, Now.

    OGB

Re: disabling unnecessary perl modules
by InfiniteSilence (Curate) on May 29, 2014 at 22:24 UTC

    Hey, in the future can you not put an entire script in Perlmonks? Wait until someone asks you for a specific piece of code rather than dump the whole thing.

    The answer to your question is going to be rather messy:

    # cat foo.pl # ----------- #!/usr/bin/perl -w use strict; use CGI; use LWP::Simple; 1; strace perl foo.pl 2> foo.pl.strace perl -ne 'if(m/(\S+\.pm)/){print qq|$1\n|}' foo.pl.strace|sort|uniq|ca +t -n

    Produces (in short):

    ... 149 stat64("/usr/lib/perl5/site_perl/5.10.0/LWP.pm 150 stat64("/usr/lib/perl5/site_perl/5.10.0/LWP/Protocol.pm 151 stat64("/usr/lib/perl5/site_perl/5.10.0/LWP/Simple.pm 152 stat64("/usr/lib/perl5/site_perl/5.10.0/LWP/UserAgent.pm 153 stat64("/usr/lib/perl5/site_perl/5.10.0/URI/Escape.pm 154 stat64("/usr/lib/perl5/site_perl/5.10.0/URI.pm 155 stat64("/usr/lib/perl5/vendor_perl/5.10.0/Encode/ConfigLocal +.pm 156 stat64("/usr/lib/perl5/vendor_perl/5.10.0/i586-linux-thread- +multi/Encode/ConfigLocal.pm 157 stat64("/usr/lib/perl5/vendor_perl/5.10.0/i586-linux-thread- +multi/Log/Agent.pm 158 stat64("/usr/lib/perl5/vendor_perl/5.10.0/Log/Agent.pm 159 stat64("/usr/lib/perl5/vendor_perl/Encode/ConfigLocal.pm 160 stat64("/usr/lib/perl5/vendor_perl/Log/Agent.pm 161 XSLoader.pm

    So, for a script that does nothing interesting and only uses two modules Perl actually checks for the presence of 161 modules. Looking at %INC only shows 37 elements for the same script, so I'm inclined to think that strace is giving me the bigger picture. Figuring out which modules you can live without should be a chore. I think you can save yourself the headache and just put all of the modules a script needs in a PAR archive and reference that.

    Celebrate Intellectual Diversity

      The reason why strace is more verbose than %INC is that Perl has to stat a file to see whether it exists. So say you've got:

      BEGIN { @INC = qw( /tmp/lib1 /tmp/lib2/ /tmp/lib3 ); } use Foo::Bar;

      And /tmp/lib3/Foo/Bar.pm is the only module that exists, Perl still has to stat /tmp/lib1/Foo/Bar.pm and /tmp/lib2/Foo/Bar.pm first. Only one actual module is loaded though.

      (Actually Perl will also stat /tmp/lib1/Foo/Bar.pmc, /tmp/lib2/Foo/Bar.pmc, and /tmp/lib3/Foo/Bar.pmc too. This is due to a rarely used feature where Perl will try to load modules with .pmc extensions ahead of .pm. The intention is that you might write your code a Bar.pm and then "compile" it to Bar.pmc for deployment. I don't mean "compile" in the same sense that C is compiled; more like what Parse::RecDescent's Precompile method does.)

      use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      Another method that might come handy, is to check access times.

      $ touch STAMP
      $ mount /usr -o remount,strictatime
      $ sh my-test-foo.sh
      $ find /usr -type f ! -anewer STAMP | xargs echo rm
      

      Your example is only looking at .pm files. Some modules also load shared libraries - usually either .so or .dll
Re: disabling unnecessary perl modules
by no_slogan (Deacon) on May 29, 2014 at 21:57 UTC

    I haven't read through that script, but you can see what modules are loaded by looking at %INC. Stick something like this in a convenient place:

    print $_,"\n" foreach sort keys %INC;
Re: disabling unnecessary perl modules
by Anonymous Monk on May 30, 2014 at 00:23 UTC
    scandeps, fatpacker, pp, perlcc, perlstrip .... 15mb isn't a lot, but you can get there by getting rid of manpages/htmlpages/pod
    $ pp -c ipacsum.pl $ du -sh a.exe 7.1M a.exe $ unzip -qd ua a.exe $ du -sh ua 14M ua

    Now the way pp/par works its 7.1M file unpacked to 14M in temp directory, but you can get there through other means ( http://cavapackager.com/ ) with same techniques par/pp uses

    Is it worth it? meh :)

Re: disabling unnecessary perl modules
by taint (Chaplain) on May 30, 2014 at 01:07 UTC
    While not a direct answer to your question, and node I intend to start in Perl::Minimal -- the good, bad, and the ugly.... It seems like there is great potential in a Module called Perl::Minimal. No?

    Just thought I'd mention it.

    --Chris

    ¡λɐp ʇɑəɹ⅁ ɐ əʌɐɥ puɐ ʻꜱdləɥ ꜱᴉɥʇ ədoH

        @Anonymous Monk

        Easy for you to say. But at the time of my response, I hadn't yet created it.

        Thanks for the mention, and (as you can probably see) it's been updated. :)

        --Chris

        ¡λɐp ʇɑəɹ⅁ ɐ əʌɐɥ puɐ ʻꜱdləɥ ꜱᴉɥʇ ədoH

      Hi, When I cross-compile perl for arm architecture, min perl is built first, but it is for the host system only where I do the cross compiling. Then min perl is used to build the one for the target system which I can actually install in the arm based system.

Re: disabling unnecessary perl modules
by boftx (Deacon) on May 29, 2014 at 23:46 UTC

    I'll echo what others have said: Why do you want to spend more in time to find an optimal build than it would cost for a bigger hard drive? You are talking about saving a couple of MB at best. If that amount of space is really a problem then delete one of the mp3's that you never listen to.

    It helps to remember that the primary goal is to drain the swamp even when you are hip-deep in alligators.

      Hi, this is for a embedded system where the root file system is restricted to a limited capacity on a NAND as a design specification

        Given that information (and being no stranger to embedded systems, albeit back in the bad old days when a 32k EEPROM was cutting edge) is it reasonable to store most of the file system as a compressed image and then extract to RAM on boot-up if you can take the hit in start-up time? What are the cost factors and trade offs?

        It helps to remember that the primary goal is to drain the swamp even when you are hip-deep in alligators.