<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package SquidAnalyzer;
#------------------------------------------------------------------------------
# Project  : Squid Log Analyzer
# Name     : SquidAnalyzer.pm
# Language : 5.005_03 built for i686-linux
# OS       : RedHat 6.2 Linux 2.2.14-5.0smp i686
# Copyright: Copyright (c) 2001-2003 : Gilles Darold / Groupe SAMSE
#            - All rights reserved -
# Licence  : This program is free software; you can redistribute it
#            and/or modify it under the same terms as Perl itself.
# Author   : Gilles Darold, gilles@darold.net
# Function : Main perl module for Squid Log Analyzer
# Usage    : See documentation.
#------------------------------------------------------------------------------
use strict;             # make things properly

BEGIN {
	use Exporter();
	use vars qw($VERSION $COPYRIGHT $AUTHOR @ISA @EXPORT @TopLevelDomain);
	use POSIX;
	use IO::File;
	use GD;
	use GD::Graph;
	use GD::Graph::bars3d;
	use GD::Graph::pie3d;

	# Set all internal variable
	$VERSION = '3.0';
	$COPYRIGHT = 'Copyright (c) 2001-2003 Gilles Darold / Groupe SAMSE - All rights reserved.';
	$AUTHOR = "Gilles Darold - gilles AT darold DOT net";

	@ISA = qw(Exporter);
	@EXPORT = qw//;

	$| = 1;

}

# Top-Level Domains - rfc 1591 (http://www.iana.org/domain-names.htm)
@TopLevelDomain = (
# Generic Domains
'.aero','.biz','.com','.coop','.edu','.gov','.info','.int','.mil','.museum','.name','.net','.org','.pro',
# Country code domains
'.ac','.ad','.ae','.af','.ag','.ai','.al','.am','.an','.ao','.aq','.ar','.as','.at','.au','.aw','.az',
'.ba','.bb','.bd','.be','.bf','.bg','.bh','.bi','.bj','.bm','.bn','.bo','.br','.bs','.bt','.bv','.bw',
'.by','.bz','.ca','.cc','.cd','.cf','.cg','.ch','.ci','.ck','.cl','.cm','.cn','.co','.cr','.cu','.cv',
'.cx','.cy','.cz','.de','.dj','.dk','.dm','.do','.dz','.ec','.ee','.eg','.eh','.er','.es','.et','.fi',
'.fj','.fk','.fm','.fo','.fr','.ga','.gd','.ge','.gf','.gg','.gh','.gi','.gl','.gm','.gn','.gp','.gq',
'.gr','.gs','.gt','.gu','.gw','.gy','.hk','.hm','.hn','.hr','.ht','.hu','.id','.ie','.il','.im','.in',
'.io','.iq','.ir','.is','.it','.je','.jm','.jo','.jp','.ke','.kg','.kh','.ki','.km','.kn','.kp','.kr',
'.kw','.ky','.kz','.la','.lb','.lc','.li','.lk','.lr','.ls','.lt','.lu','.lv','.ly','.ma','.mc','.md',
'.mg','.mh','.mk','.ml','.mm','.mn','.mo','.mp','.mq','.mr','.ms','.mt','.mu','.mv','.mw','.mx','.my',
'.mz','.na','.nc','.ne','.nf','.ng','.ni','.nl','.no','.np','.nr','.nu','.nz','.om','.pa','.pe','.pf',
'.pg','.ph','.pk','.pl','.pm','.pn','.pr','.ps','.pt','.pw','.py','.qa','.re','.ro','.ru','.rw','.sa',
'.sb','.sc','.sd','.se','.sg','.sh','.si','.sj','.sk','.sl','.sm','.sn','.so','.sr','.st','.sv','.sy',
'.sz','.tc','.td','.tf','.tg','.th','.tj','.tk','.tm','.tn','.to','.tp','.tr','.tt','.tv','.tw','.tz',
'.ua','.ug','.uk','.um','.us','.uy','.uz','.va','.vc','.ve','.vg','.vi','.vn','.vu','.wf','.ws','.ye',
'.yt','.yu','.za','.zm','.zw'
);

# Default translation srings
my %Translate = (
	'01' =&gt; 'Jan',
	'02' =&gt; 'Feb',
	'03' =&gt; 'Mar',
	'04' =&gt; 'Apr',
	'05' =&gt; 'May',
	'06' =&gt; 'Jun',
	'07' =&gt; 'Jul',
	'08' =&gt; 'Aug',
	'09' =&gt; 'Sep',
	'10' =&gt; 'Oct',
	'11' =&gt; 'Nov',
	'12' =&gt; 'Dec',
	'Bytes' =&gt; 'Bytes',
	'Total' =&gt; 'Total',
	'Years' =&gt; 'Years',
	'Users' =&gt; 'Users',
	'Sites' =&gt; 'Sites',
	'Cost' =&gt; 'Cost',
	'Requests' =&gt; 'Requests',
	'Megabytes' =&gt; 'Mega bytes',
	'Months' =&gt; 'Months',
	'Days' =&gt; 'Days',
	'Hit' =&gt; 'Hit',
	'Miss' =&gt; 'Miss',
	'Domains' =&gt; 'Domains',
	'Requests_graph' =&gt; 'Requests',
	'Megabytes_graph' =&gt; 'Mega bytes',
	'Months_graph' =&gt; 'Months',
	'Days_graph' =&gt; 'Days',
	'Hit_graph' =&gt; 'Hit',
	'Miss_graph' =&gt; 'Miss',
	'Total_graph' =&gt; 'Total',
	'Domains_graph' =&gt; 'Domains',
	'Users_help' =&gt; 'Total number of different users for this period',
	'Sites_help' =&gt; 'Total number of different visited sites for this period',
	'Domains_help' =&gt; 'Total number of different second level visited domain for this period',
	'Hit_help' =&gt; 'Objects found in cache',
	'Miss_help' =&gt; 'Objects not found in cache',
	'Cost_help' =&gt; '1 Mega byte =',
	'Generation' =&gt; 'Report generated on',
	'Main_cache_title' =&gt; 'Cache Statistics',
	'Cache_title' =&gt; 'Cache Statistics on',
	'Stat_label' =&gt; 'Stat',
	'Mime_link' =&gt; 'Mime Types',
	'Network_link' =&gt; 'Networks',
	'User_link' =&gt; 'Users',
	'Top_url_link' =&gt; 'Top Urls',
	'Top_domain_link' =&gt; 'Top Domains',
	'Back_link' =&gt; 'Back',
	'Graph_cache_hit_title' =&gt; '%s Requests statistics on',
	'Graph_cache_byte_title' =&gt; '%s Mega Bytes statistics on',
	'Hourly' =&gt; 'Hourly',
	'Hours' =&gt; 'Hours',
	'Daily' =&gt; 'Daily',
	'Days' =&gt; 'Days',
	'Monthly' =&gt; 'Monthly',
	'Months' =&gt; 'Months',
	'Mime_title' =&gt; 'Mime Type Statistics on',
	'Mime_number' =&gt; 'Number of mime type',
	'Network_title' =&gt; 'Network Statistics on',
	'Network_number' =&gt; 'Number of network',
	'Duration' =&gt; 'Duration',
	'Time' =&gt; 'Time',
	'Largest' =&gt; 'Largest',
	'Url' =&gt; 'Url',
	'User_title' =&gt; 'User Statistics on',
	'User_number' =&gt; 'Number of user',
	'Url_Hits_title' =&gt; 'Top %d Url hits on',
	'Url_Bytes_title' =&gt; 'Top %d Url bytes on',
	'Url_Duration_title' =&gt; 'Top %d Url duration on',
	'Url_number' =&gt; 'Number of Url',
	'Domain_Hits_title' =&gt; 'Top %d Domain hits on',
	'Domain_Bytes_title' =&gt; 'Top %d Domain bytes on',
	'Domain_Duration_title' =&gt; 'Top %d Domain duration on',
	'Domain_number' =&gt; 'Number of domain',
	'Domain_graph_hits_title' =&gt; 'Domain Hits Statistics on',
	'Domain_graph_bytes_title' =&gt; 'Domain Bytes Statistiques on',
);


sub new
{
	my($class, $conf_file) = @_;

	# Construct the class
	my $self = {};
	bless $self, $class;

	# Initialize all variables
	$self-&gt;_init($conf_file);

	# Return the instance
	return($self);

}

sub parseFile
{
	my ($self) = @_;

	return if ((!-f $self-&gt;{LogFile}) || (-z $self-&gt;{LogFile}));

	# The log file format must be :
	# 	time elapsed client code/status bytes method URL rfc931 peerstatus/peerhost type
	# This is the default format of squid access log file.

	# Open logfile
	my $logfile = new IO::File;
	$logfile-&gt;open($self-&gt;{LogFile}) || die "ERROR: Unable to open Squid access.log file $self-&gt;{LogFile}. $!\n";

	# Read and parse each line of the access log file
	while (my $line = &lt;$logfile&gt;) {
		chomp($line);

		# Parse log with format: time elapsed client code/status bytes method URL rfc931 peerstatus/peerhost mime_type
		if ( $line =~ s#^(\d+\.\d{3})\s+(\d+)\s+(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+([^\s]+)\s+(\d+)\s+([^\s]+)\s+## ) {
			my $time = $1 || 0;
			my $elapsed = $2 || 0;
			my $client_ip = $3 || '';
			my $code = $4 || '';
			my $bytes = $5 || 0;
			my $method = $6 || '';

			# Go to last parsed date (incremental mode)
			next if ($self-&gt;{history_time} &amp;&amp; ($time &lt;= $self-&gt;{history_time}));

			# Register the last parsing time
			$self-&gt;{end_time} = $time;

			# Register the first parsing time
			if (!$self-&gt;{begin_time}) {
				$self-&gt;{begin_time} = $time;
				print STDERR "START TIME: ", strftime("%a %b %e %H:%M:%S %Y", localtime($time)), "\n" if (!$self-&gt;{QuietMode});
			}
			# Only store HIT/MISS status
			if ($code =~ m#HIT/#) {
				$code = 'HIT';
			} elsif ($code =~ m#MISS/#) {
				$code = 'MISS';
			} else {
				next;
			}

			if ( $line =~ s#^(.*)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)$## ) {
				my $url = lc($1) || '';
				my $login = lc($2) || '';
				my $status = lc($3) || '';
				my $mime_type = lc($4) || '';
				$mime_type = 'none' if (!$mime_type || ($mime_type eq '-'));
				# Remove extra space character in username
				$login =~ s/\%20//g;

				my $id = $client_ip || '';
				if ($login ne '-') {
					$id = $login;
				}
				next if (!$id || !$bytes);
				# Skip exclusion of client
				next if (grep(/^($client_ip|$login)$/, @{$self-&gt;{Exclude}}));
				# Skip exclusion of network
				$client_ip =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.)/;
				my $netex = $1 . '0';
				next if (grep(/^$netex$/, @{$self-&gt;{Exclude}}));
				
				# Now parse data and generate statistics
				$self-&gt;_parseData($time, $elapsed, $client_ip, $code, $bytes, $url, $id, $mime_type);

			}
		}

	}
	$logfile-&gt;close();

	if (!$self-&gt;{last_year} &amp;&amp; !$self-&gt;{last_month} &amp;&amp; !$self-&gt;{last_day}) {
		print STDERR "No new log registered...\n" if (!$self-&gt;{QuietMode});
		exit 0;
	}
	# Save last parsed data
	$self-&gt;_save_data("$self-&gt;{last_year}", "$self-&gt;{last_month}", "$self-&gt;{last_day}");

	if (!$self-&gt;{QuietMode}) {
		print STDERR "END TIME  : ", strftime("%a %b %e %H:%M:%S %Y", localtime($self-&gt;{end_time})), "\n";
	}

	# Set the current start time into history file
	if ($self-&gt;{end_time}) {
		my $current = new IO::File;
		$current-&gt;open("&gt;$self-&gt;{Output}/SquidAnalyzer.current") or die "Error: Can't write to file $self-&gt;{Output}/SquidAnalyzer.current, $!\n";
		print $current "$self-&gt;{end_time}";
		$current-&gt;close;
	}

	# Compute month statistics
	if (!$self-&gt;{QuietMode}) {
		print STDERR "\nParsing ended, generating data files now...\n";
		print STDERR "Compute and dump month statistics for $self-&gt;{first_year}/$self-&gt;{first_month} to $self-&gt;{last_year}/$self-&gt;{last_month}\n";
	}
	for my $date ("$self-&gt;{first_year}$self-&gt;{first_month}" .. "$self-&gt;{last_year}$self-&gt;{last_month}") {
		$date =~ /^(\d{4})(\d{2})$/;
		$self-&gt;_save_data("$1", "$2");
	}

	# Compute year statistics
	if (!$self-&gt;{QuietMode}) {
		print STDERR "Compute and dump year statistics for $self-&gt;{first_year} to $self-&gt;{last_year}\n";
	}
	for my $year ("$self-&gt;{first_year}" .. "$self-&gt;{last_year}") {
		$self-&gt;_save_data($year);
	}

}

sub _init
{
	my ($self, $conf_file) = @_;

	# Prevent for a call without instance
	if (!ref($self))
	{
		print "ERROR - init : Unable to call init without an object instance.\n";
		exit(0);
	}

	# Load configuration information
	if (!$conf_file) {
		if (-f '/etc/squidanalyzer.conf') {
			$conf_file = '/etc/squidanalyzer.conf';
		} elsif (-f 'squidanalyzer.conf') {
			$conf_file = 'squidanalyzer.conf';
		}
	}
	my %options = &amp;parse_config($conf_file);

	# Configuration options
	$self-&gt;{QuietMode} = $options{QuietMode} || 0;
	$self-&gt;{UrlReport} = $options{UrlReport} || 0;
	$self-&gt;{Output} = $options{Output} || '';
	$self-&gt;{DateFormat} = $options{DateFormat} || '%y-%m-%d';
	$self-&gt;{Lang} = $options{Lang} || '';
	$self-&gt;{HeaderFile} = $options{HeaderFile} || '';
	$self-&gt;{FooterFile} = $options{FooterFile} || '';
	if ($self-&gt;{Lang}) {
		open(IN, "$self-&gt;{Lang}") or die "ERROR: can't open translation file $self-&gt;{Lang}, $!\n";
		while (my $l = &lt;IN&gt;) {
			chomp($l);
			next if ($l =~ /^[\s\t]*#/);
			next if (!$l);
			my ($key, $str) = split(/\t+/, $l);
			$Translate{$key} = $str;
		}
		close(IN);
	}
	if ($self-&gt;{HeaderFile}) {
		open(IN, "$self-&gt;{HeaderFile}") or die "ERROR: can't open header file $self-&gt;{HeaderFile}, $!\n";
		$self-&gt;{HeaderFile} = '';
		while (my $l = &lt;IN&gt;) {
			$self-&gt;{HeaderFile} .= $l;
		}
		close(IN);
	}
	if ($self-&gt;{FooterFile}) {
		open(IN, "$self-&gt;{FooterFile}") or die "ERROR: can't open footer file $self-&gt;{FooterFile}, $!\n";
		$self-&gt;{FooterFile} = '';
		while (my $l = &lt;IN&gt;) {
			$self-&gt;{FooterFile} .= $l;
		}
		close(IN);
	}
	if (!$self-&gt;{Output}) {
		die "ERROR: 'Output' configuration option must be set.\n";
	}
	if (! -d $self-&gt;{Output}) {
		die "ERROR: 'Output' dorectory $self-&gt;{Output} doesn't exists.\n";
	}
	$self-&gt;{LogFile} = $options{LogFile} || '/var/log/squid/access.log';
	if (!$self-&gt;{LogFile}) {
		die "ERROR: 'LogFile' configuration option must be set.\n";
	}
	if (! -e $self-&gt;{LogFile}) {
		die "ERROR: 'LogFile' $self-&gt;{LogFile} doesn't exists.\n";
	}
	$self-&gt;{OrderUser} = lc($options{OrderUser}) || 'bytes';
	$self-&gt;{OrderNetwork} = lc($options{OrderNetwork}) || 'bytes';
	$self-&gt;{OrderUrl} = lc($options{OrderUrl}) || 'bytes';
	$self-&gt;{OrderMime} = lc($options{OrderMime}) || 'bytes';
	if ($self-&gt;{OrderUser} !~ /^(hits|bytes|duration)$/) {
		die "ERROR: OrderUser must be one of these values: hits, bytes or duration\n";
	}
	if ($self-&gt;{OrderNetwork} !~ /^(hits|bytes|duration)$/) {
		die "ERROR: OrderNetwork must be one of these values: hits, bytes or duration\n";
	}
	if ($self-&gt;{OrderUrl} !~ /^(hits|bytes|duration)$/) {
		die "ERROR: OrderUrl must be one of these values: hits, bytes or duration\n";
	}
	if ($self-&gt;{OrderMime} !~ /^(hits|bytes)$/) {
		die "ERROR: OrderMime must be one of these values: hits or bytes\n";
	}
	$self-&gt;{NetworkAlias} = &amp;parse_network_aliases($options{NetworkAlias} || '');
	$self-&gt;{UserAlias} = &amp;parse_user_aliases($options{UserAlias} || '');
	$self-&gt;{Exclude} = &amp;parse_user_exclusion($options{Exclude} || '');

	$self-&gt;{CostPrice} = $options{CostPrice} || 0;
	$self-&gt;{Currency} = $options{Currency} || '&amp;euro;';
	$self-&gt;{TopNumber} = $options{TopNumber} || 10;

	# Hashes to store user statistics
	$self-&gt;{stat_user_hour} = ();
	$self-&gt;{stat_user_day} = ();
	$self-&gt;{stat_user_month} = ();
	$self-&gt;{stat_usermax_hour} = ();
	$self-&gt;{stat_usermax_day} = ();
	$self-&gt;{stat_usermax_month} = ();
	$self-&gt;{stat_user_url_hour} = ();
	$self-&gt;{stat_user_url_day} = ();
	$self-&gt;{stat_user_url_month} = ();

	# Hash to store network statistics
	$self-&gt;{stat_network_hour} = ();
	$self-&gt;{stat_network_day} = ();
	$self-&gt;{stat_network_month} = ();
	$self-&gt;{stat_netmax_hour} = ();
	$self-&gt;{stat_netmax_day} = ();
	$self-&gt;{stat_netmax_month} = ();

	# Hash to store user / network statistics
	$self-&gt;{stat_netuser_hour} = ();
	$self-&gt;{stat_netuser_day} = ();
	$self-&gt;{stat_netuser_month} = ();

	# Hash to store cache status (hit/miss)
	$self-&gt;{stat_code_hour} = ();
	$self-&gt;{stat_code_day} = ();
	$self-&gt;{stat_code_month} = ();

	# Hast to store mime type
	$self-&gt;{stat_mime_type_hour} = ();
	$self-&gt;{stat_mime_type_day} = ();
	$self-&gt;{stat_mime_type_month} = ();

	# Used to store the first and last date parsed
        $self-&gt;{last_year} = 0;
        $self-&gt;{last_month} = 0;
        $self-&gt;{last_day} = 0;
        $self-&gt;{first_year} = 0;
        $self-&gt;{first_month} = 0;
        $self-&gt;{first_day} = 0;
	$self-&gt;{begin_time} = 0;
	$self-&gt;{end_time} = 0;
	$self-&gt;{history_time} = 0;

	# Get the last parsing date for incremental parsing
	if (-e "$self-&gt;{Output}/SquidAnalyzer.current") {
		my $current = new IO::File;
		unless($current-&gt;open("$self-&gt;{Output}/SquidAnalyzer.current")) {
			print STDERR "ERROR: Can't read file $self-&gt;{Output}/SquidAnalyzer.current, $!\n" if (!$self-&gt;{QuietMode});
			print STDERR "Starting at the first line of Squid access log file.\n" if (!$self-&gt;{QuietMode});
		} else {
			$self-&gt;{history_time} = &lt;$current&gt;;
			chomp($self-&gt;{history_time});
			$self-&gt;{begin_time} = $self-&gt;{history_time};
			$current-&gt;close();
			print STDERR "HISTORY TIME: ", strftime("%a %b %e %H:%M:%S %Y", localtime($self-&gt;{history_time})), "\n" if (!$self-&gt;{QuietMode});
		}
	}

}

sub _parseData
{
	my ($self, $time, $elapsed, $client, $code, $bytes, $url, $id, $type) = @_;

	# Get the current year and month
	my ($sec,$min,$hour,$wday,$yday,$isdst) = '';
	($sec,$min,$hour,$self-&gt;{last_day},$self-&gt;{last_month},$self-&gt;{last_year},$wday,$yday,$isdst) = localtime($time);
	$self-&gt;{last_year} += 1900;
	$self-&gt;{last_month}++;
	$self-&gt;{last_month} = "0$self-&gt;{last_month}" if ($self-&gt;{last_month} &lt; 10);
	$self-&gt;{last_day} = "0$self-&gt;{last_day}" if ($self-&gt;{last_day} &lt; 10);
	$hour = "0$hour" if ($hour &lt; 10);

	# Set the year/month value for history check
	my $date = "$self-&gt;{last_year}$self-&gt;{last_month}$self-&gt;{last_day}";

	# Extract the domainname part of the URL
	my $dest = $url;
	$dest =~ s#^[^\/]*\/\/##;
	$dest =~ s#\/.*##;
	$dest =~ s#:\d+$##;

	# Extract network part of the client address
	my $network = $client;
	$network =~ s/\.\d+$//;

	# Store data when day change to save memory
	if ($self-&gt;{tmp_saving} &amp;&amp; ($self-&gt;{tmp_saving} ne "$date")) {
		$self-&gt;{tmp_saving} =~ /^(\d{4})(\d{2})(\d{2})$/;
		$self-&gt;_save_data("$1", "$2", "$3");
	}

	$self-&gt;{first_year} ||= $self-&gt;{last_year};
	$self-&gt;{first_month} ||= $self-&gt;{last_month};
	$self-&gt;{first_day} ||= $self-&gt;{last_day};

	$self-&gt;{tmp_saving} = $date;

	#### Store client statistics
	$self-&gt;{stat_user_hour}{$id}{$hour}{hits}++;
	$self-&gt;{stat_user_hour}{$id}{$hour}{bytes} += $bytes;
	$self-&gt;{stat_user_hour}{$id}{$hour}{duration} += $elapsed;
	$self-&gt;{stat_user_day}{$id}{$self-&gt;{last_day}}{hits}++;
	$self-&gt;{stat_user_day}{$id}{$self-&gt;{last_day}}{bytes} += $bytes;
	$self-&gt;{stat_user_day}{$id}{$self-&gt;{last_day}}{duration} += $elapsed;
	$self-&gt;{stat_user_month}{$id}{$self-&gt;{last_month}}{hits}++;
	$self-&gt;{stat_user_month}{$id}{$self-&gt;{last_month}}{bytes} += $bytes;
	$self-&gt;{stat_user_month}{$id}{$self-&gt;{last_month}}{duration} += $elapsed;
	if ($bytes &gt; $self-&gt;{stat_usermax_hour}{$id}{largest_file_size}) {
		$self-&gt;{stat_usermax_hour}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_usermax_hour}{$id}{largest_file_url} = $url;
	}
	if ($bytes &gt; $self-&gt;{stat_usermax_day}{$id}{largest_file_size}) {
		$self-&gt;{stat_usermax_day}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_usermax_day}{$id}{largest_file_url} = $url;
	}
	if ($bytes &gt; $self-&gt;{stat_usermax_month}{$id}{largest_file_size}) {
		$self-&gt;{stat_usermax_month}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_usermax_month}{$id}{largest_file_url} = $url;
	}

	#### Store networks statistics
	$self-&gt;{stat_network_hour}{$network}{$hour}{hits}++;
	$self-&gt;{stat_network_hour}{$network}{$hour}{bytes} += $bytes;
	$self-&gt;{stat_network_hour}{$network}{$hour}{duration} += $elapsed;
	$self-&gt;{stat_network_day}{$network}{$self-&gt;{last_day}}{hits}++;
	$self-&gt;{stat_network_day}{$network}{$self-&gt;{last_day}}{bytes} += $bytes;
	$self-&gt;{stat_network_day}{$network}{$self-&gt;{last_day}}{duration} += $elapsed;
	$self-&gt;{stat_network_month}{$network}{$self-&gt;{last_month}}{hits}++;
	$self-&gt;{stat_network_month}{$network}{$self-&gt;{last_month}}{bytes} += $bytes;
	$self-&gt;{stat_network_month}{$network}{$self-&gt;{last_month}}{duration} += $elapsed;
	if ($bytes &gt; $self-&gt;{stat_netmax_hour}{$network}{largest_file_size}) {
		$self-&gt;{stat_netmax_hour}{$network}{largest_file_size} = $bytes;
		$self-&gt;{stat_netmax_hour}{$network}{largest_file_url} = $url;
	}
	if ($bytes &gt; $self-&gt;{stat_netmax_day}{$network}{largest_file_size}) {
		$self-&gt;{stat_netmax_day}{$network}{largest_file_size} = $bytes;
		$self-&gt;{stat_netmax_day}{$network}{largest_file_url} = $url;
	}
	if ($bytes &gt; $self-&gt;{stat_netmax_month}{$network}{largest_file_size}) {
		$self-&gt;{stat_netmax_month}{$network}{largest_file_size} = $bytes;
		$self-&gt;{stat_netmax_month}{$network}{largest_file_url} = $url;
	}

	#### Store HIT/MISS statistics
	$self-&gt;{stat_code_hour}{$code}{$hour}{hits}++;
	$self-&gt;{stat_code_hour}{$code}{$hour}{bytes} += $bytes;
	$self-&gt;{stat_code_day}{$code}{$self-&gt;{last_day}}{hits}++;
	$self-&gt;{stat_code_day}{$code}{$self-&gt;{last_day}}{bytes} += $bytes;
	$self-&gt;{stat_code_month}{$code}{$self-&gt;{last_month}}{hits}++;
	$self-&gt;{stat_code_month}{$code}{$self-&gt;{last_month}}{bytes} += $bytes;

	#### Store url statistics
	if ($self-&gt;{UrlReport}) {
		$self-&gt;{stat_user_url_hour}{$id}{$dest}{duration} += $elapsed;
		$self-&gt;{stat_user_url_hour}{$id}{$dest}{hits}++;
		$self-&gt;{stat_user_url_hour}{$id}{$dest}{bytes} += $bytes;
		$self-&gt;{stat_user_url_day}{$id}{$dest}{duration} += $elapsed;
		$self-&gt;{stat_user_url_day}{$id}{$dest}{hits}++;
		$self-&gt;{stat_user_url_day}{$id}{$dest}{bytes} += $bytes;
		$self-&gt;{stat_user_url_month}{$id}{$dest}{duration} += $elapsed;
		$self-&gt;{stat_user_url_month}{$id}{$dest}{hits}++;
		$self-&gt;{stat_user_url_month}{$id}{$dest}{bytes} += $bytes;
	}

	#### Store user per networks statistics
	$self-&gt;{stat_netuser_hour}{$network}{$id}{duration} += $elapsed;
	$self-&gt;{stat_netuser_hour}{$network}{$id}{bytes} += $bytes;
	$self-&gt;{stat_netuser_hour}{$network}{$id}{hits}++;
	if ($bytes &gt; $self-&gt;{stat_netuser_hour}{$network}{$id}{largest_file_size}) {
		$self-&gt;{stat_netuser_hour}{$network}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_netuser_hour}{$network}{$id}{largest_file_url} = $url;
	}
	$self-&gt;{stat_netuser_day}{$network}{$id}{duration} += $elapsed;
	$self-&gt;{stat_netuser_day}{$network}{$id}{bytes} += $bytes;
	$self-&gt;{stat_netuser_day}{$network}{$id}{hits}++;
	if ($bytes &gt; $self-&gt;{stat_netuser_day}{$network}{$id}{largest_file_size}) {
		$self-&gt;{stat_netuser_day}{$network}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_netuser_day}{$network}{$id}{largest_file_url} = $url;
	}
	$self-&gt;{stat_netuser_month}{$network}{$id}{duration} += $elapsed;
	$self-&gt;{stat_netuser_month}{$network}{$id}{bytes} += $bytes;
	$self-&gt;{stat_netuser_month}{$network}{$id}{hits}++;
	if ($bytes &gt; $self-&gt;{stat_netuser_month}{$network}{$id}{largest_file_size}) {
		$self-&gt;{stat_netuser_month}{$network}{$id}{largest_file_size} = $bytes;
		$self-&gt;{stat_netuser_month}{$network}{$id}{largest_file_url} = $url;
	}

	#### Store mime type statistics
	$self-&gt;{stat_mime_type_hour}{"$type"}{hits}++;
	$self-&gt;{stat_mime_type_hour}{"$type"}{bytes} += $bytes;
	$self-&gt;{stat_mime_type_day}{"$type"}{hits}++;
	$self-&gt;{stat_mime_type_day}{"$type"}{bytes} += $bytes;
	$self-&gt;{stat_mime_type_month}{"$type"}{hits}++;
	$self-&gt;{stat_mime_type_month}{"$type"}{bytes} += $bytes;

}

sub _save_stat
{
	my ($self, $year, $month, $day) = @_;

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	my $path = join('/', $year, $month, $day);
	$path =~ s/[\/]+$//;

	#### Load history
	$self-&gt;_read_stat($year, $month, $day);

	#### Save url statistics per user
	if ($self-&gt;{UrlReport}) {
		my $dat_file_user_url = new IO::File;
		$dat_file_user_url-&gt;open("&gt;$self-&gt;{Output}/$path/stat_user_url.dat")
			or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_user_url.dat, $!\n";
		foreach my $id (sort {$a cmp $b} keys %{$self-&gt;{"stat_user_url_$type"}}) {
			foreach my $dest (keys %{$self-&gt;{"stat_user_url_$type"}{$id}}) {
				$dat_file_user_url-&gt;print("$id hits=" . $self-&gt;{"stat_user_url_$type"}{$id}{$dest}{hits} . ";" .
					"bytes=" . $self-&gt;{"stat_user_url_$type"}{$id}{$dest}{bytes} . ";" .
					"duration=" . $self-&gt;{"stat_user_url_$type"}{$id}{$dest}{duration} . ";" .
					"url=$dest\n");
			}
		}
		$dat_file_user_url-&gt;close();
		$self-&gt;{"stat_user_url_$type"} = ();
	}

	#### Save user statistics
	my $dat_file_user = new IO::File;
	$dat_file_user-&gt;open("&gt;$self-&gt;{Output}/$path/stat_user.dat")
		or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_user.dat, $!\n";
	foreach my $id (sort {$a cmp $b} keys %{$self-&gt;{"stat_user_$type"}}) {
		$dat_file_user-&gt;print("$id hits_$type="); 
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_user_$type"}{$id}}) {
			$dat_file_user-&gt;print("$tmp:" . $self-&gt;{"stat_user_$type"}{$id}{$tmp}{hits} . ",");
		}
		$dat_file_user-&gt;print(";bytes_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_user_$type"}{$id}}) {
			$dat_file_user-&gt;print("$tmp:" . $self-&gt;{"stat_user_$type"}{$id}{$tmp}{bytes} . ",");
		}
		$dat_file_user-&gt;print(";duration_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_user_$type"}{$id}}) {
			$dat_file_user-&gt;print("$tmp:" . $self-&gt;{"stat_user_$type"}{$id}{$tmp}{duration} . ",");
		}
		$dat_file_user-&gt;print(";largest_file_size=" . $self-&gt;{"stat_usermax_$type"}{$id}{largest_file_size});
		$dat_file_user-&gt;print(";largest_file_url=" . $self-&gt;{"stat_usermax_$type"}{$id}{largest_file_url} . "\n");
	}
	$dat_file_user-&gt;close();
	$self-&gt;{"stat_user_$type"} = ();
	$self-&gt;{"stat_usermax_$type"} = ();

	#### Save network statistics
	my $dat_file_network = new IO::File;
	$dat_file_network-&gt;open("&gt;$self-&gt;{Output}/$path/stat_network.dat")
		or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_network.dat, $!\n";
	foreach my $net (sort {$a cmp $b} keys %{$self-&gt;{"stat_network_$type"}}) {
		$dat_file_network-&gt;print("$net hits_$type="); 
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_network_$type"}{$net}}) {
			$dat_file_network-&gt;print("$tmp:" . $self-&gt;{"stat_network_$type"}{$net}{$tmp}{hits} . ",");
		}
		$dat_file_network-&gt;print(";bytes_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_network_$type"}{$net}}) {
			$dat_file_network-&gt;print("$tmp:" . $self-&gt;{"stat_network_$type"}{$net}{$tmp}{bytes} . ",");
		}
		$dat_file_network-&gt;print(";duration_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_network_$type"}{$net}}) {
			$dat_file_network-&gt;print("$tmp:" . $self-&gt;{"stat_network_$type"}{$net}{$tmp}{duration} . ",");
		}
		$dat_file_network-&gt;print(";largest_file_size=" . $self-&gt;{"stat_netmax_$type"}{$net}{largest_file_size});
		$dat_file_network-&gt;print(";largest_file_url=" . $self-&gt;{"stat_netmax_$type"}{$net}{largest_file_url} . "\n");
	}
	$dat_file_network-&gt;close();
	$self-&gt;{"stat_network_$type"} = ();
	$self-&gt;{"stat_netmax_$type"} = ();

	#### Save user per network statistics
	my $dat_file_netuser = new IO::File;
	$dat_file_netuser-&gt;open("&gt;$self-&gt;{Output}/$path/stat_netuser.dat")
		or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_netuser.dat, $!\n";
	foreach my $net (sort {$a cmp $b} keys %{$self-&gt;{"stat_netuser_$type"}}) {
		foreach my $id (sort {$a cmp $b} keys %{$self-&gt;{"stat_netuser_$type"}{$net}}) {
			$dat_file_netuser-&gt;print("$net $id hits=" . $self-&gt;{"stat_netuser_$type"}{$net}{$id}{hits} . ";" .
				"bytes=" . $self-&gt;{"stat_netuser_$type"}{$net}{$id}{bytes} . ";" .
				"duration=" . $self-&gt;{"stat_netuser_$type"}{$net}{$id}{duration} . ";");
			$dat_file_netuser-&gt;print("largest_file_size=" .
				$self-&gt;{"stat_netuser_$type"}{$net}{$id}{largest_file_size} . ";" .
				"largest_file_url=" . $self-&gt;{"stat_netuser_$type"}{$net}{$id}{largest_file_url} . "\n");
		}
	}
	$dat_file_netuser-&gt;close();
	$self-&gt;{"stat_netuser_$type"} = ();


	#### Save cache statistics
	my $dat_file_code = new IO::File;
	$dat_file_code-&gt;open("&gt;$self-&gt;{Output}/$path/stat_code.dat")
		or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_code.dat, $!\n";
	foreach my $code (sort {$a cmp $b} keys %{$self-&gt;{"stat_code_$type"}}) {
		$dat_file_code-&gt;print("$code " .
			"hits_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_code_$type"}{$code}}) {
			$dat_file_code-&gt;print("$tmp:" . $self-&gt;{"stat_code_$type"}{$code}{$tmp}{hits} . ",");
		}
		$dat_file_code-&gt;print(";bytes_$type=");
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %{$self-&gt;{"stat_code_$type"}{$code}}) {
			$dat_file_code-&gt;print("$tmp:" . $self-&gt;{"stat_code_$type"}{$code}{$tmp}{bytes} . ",");
		}
		$dat_file_code-&gt;print("\n");
	}
	$dat_file_code-&gt;close();
	$self-&gt;{"stat_code_$type"} = ();
	$self-&gt;{stat_code} = ();

	#### Save mime statistics
	my $dat_file_mime_type = new IO::File;
	$dat_file_mime_type-&gt;open("&gt;$self-&gt;{Output}/$path/stat_mime_type.dat")
		or die "ERROR: Can't write to file $self-&gt;{Output}/$path/stat_mime_type.dat, $!\n";
	foreach my $mime (sort {$a cmp $b} keys %{$self-&gt;{"stat_mime_type_$type"}}) {
		$dat_file_mime_type-&gt;print("$mime hits=" . $self-&gt;{"stat_mime_type_$type"}{$mime}{hits} . ";" .
			"bytes=" . $self-&gt;{"stat_mime_type_$type"}{$mime}{bytes} .  "\n");
	}
	$dat_file_mime_type-&gt;close();
	$self-&gt;{"stat_mime_type_$type"} = ();

}

sub _save_data
{
	my ($self, $year, $month, $day) = @_;

	my $path = join('/', $year, $month, $day);
	$path =~ s/[\/]+$//;

	#### Create directory structure
	if (!-d "$self-&gt;{Output}/$year") {
		mkdir("$self-&gt;{Output}/$year", 0755) || die "ERROR: can't create directory $self-&gt;{Output}/$year, $!\n";
	}
	if ($month &amp;&amp; !-d "$self-&gt;{Output}/$year/$month") {
		mkdir("$self-&gt;{Output}/$year/$month", 0755) || die "ERROR: can't create directory $self-&gt;{Output}/$year/$month, $!\n";
	}
	if ($day &amp;&amp; !-d "$self-&gt;{Output}/$year/$month/$day") {
		mkdir("$self-&gt;{Output}/$year/$month/$day", 0755) || die "ERROR: can't create directory $self-&gt;{Output}/$year/$month/$day, $!\n";
	}
	print STDERR "Dumping data into $self-&gt;{Output}/$path\n" if (!$self-&gt;{QuietMode});
	$self-&gt;_save_stat($year, $month, $day);

}


sub _read_stat
{
	my ($self, $year, $month, $day) = @_;

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	my $path = join('/',  $year, $month, $day);
	$path =~ s/[\/]+$//;

	#### Read previous client statistics
	my $dat_file_user = new IO::File;
	if ($dat_file_user-&gt;open("$self-&gt;{Output}/$path/stat_user.dat")) {
		while (my $l = &lt;$dat_file_user&gt;) {
			chomp($l);
			if ($l =~ s/^([^\s]+)\shits_$type=([^;]+);bytes_$type=([^;]+);duration_$type=([^;]+);largest_file_size=(\d+);largest_file_url=(.*)$//) {
				my $id = $1;
				my $hits = $2 || '';
				my $bytes = $3 || '';
				my $duration = $4 || '';
				if ($5 &gt; $self-&gt;{"stat_usermax_$type"}{$id}{largest_file_size}) {
					$self-&gt;{"stat_usermax_$type"}{$id}{largest_file_size} = $5;
					$self-&gt;{"stat_usermax_$type"}{$id}{largest_file_url} = $6;
				}
				$hits =~ s/,$//;	
				$bytes =~ s/,$//;	
				$duration =~ s/,$//;	
				my %hits_tmp = split(/[:,]/, $hits);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
					$self-&gt;{"stat_user_$type"}{$id}{$tmp}{hits} += $hits_tmp{$tmp};
				}
				my %bytes_tmp = split(/[:,]/, $bytes);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
					$self-&gt;{"stat_user_$type"}{$id}{$tmp}{bytes} += $bytes_tmp{$tmp};
				}
				my %duration_tmp = split(/[:,]/, $duration);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %duration_tmp) {
					$self-&gt;{"stat_user_$type"}{$id}{$tmp}{duration} += $duration_tmp{$tmp};
				}
			} else {
				print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_user.dat:\n";
				print STDERR "$l\n";
				exit 0;
			}
		}
		$dat_file_user-&gt;close();
	}

	#### Read previous url statistics
	if ($self-&gt;{UrlReport}) {
		my $dat_file_user_url = new IO::File;
		if ($dat_file_user_url-&gt;open("$self-&gt;{Output}/$path/stat_user_url.dat")) {
			while (my $l = &lt;$dat_file_user_url&gt;) {
				chomp($l);
				if ($l =~ s/^([^\s]+)\shits=(\d+);bytes=(\d+);duration=(\d+);url=(.*)$//) {
					$self-&gt;{"stat_user_url_$type"}{$1}{"$5"}{hits} += $2;
					$self-&gt;{"stat_user_url_$type"}{$1}{"$5"}{bytes} += $3;
					$self-&gt;{"stat_user_url_$type"}{$1}{"$5"}{duration} += $4;
				} else {
					print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_user_url.dat\n";
					print STDERR "$l\n";
					exit 0;
				}
			}
			$dat_file_user_url-&gt;close();
		}
	}

	#### Read previous network statistics
	my $dat_file_network = new IO::File;
	if ($dat_file_network-&gt;open("$self-&gt;{Output}/$path/stat_network.dat")) {
		while (my $l = &lt;$dat_file_network&gt;) {
			chomp($l);
			if ($l =~ s/^([^\s]+)\shits_$type=([^;]+);bytes_$type=([^;]+);duration_$type=([^;]+);largest_file_size=(\d+);largest_file_url=(.*)$//) {
				my $net = $1;
				my $hits = $2 || '';
				my $bytes = $3 || '';
				my $duration = $4 || '';
				if ($5 &gt; $self-&gt;{"stat_netmax_$type"}{$net}{largest_file_size}) {
					$self-&gt;{"stat_netmax_$type"}{$net}{largest_file_size} = $5;
					$self-&gt;{"stat_netmax_$type"}{$net}{largest_file_url} = $6;
				}
				$hits =~ s/,$//;	
				$bytes =~ s/,$//;	
				$duration =~ s/,$//;	
				my %hits_tmp = split(/[:,]/, $hits);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
					$self-&gt;{"stat_network_$type"}{$net}{$tmp}{hits} += $hits_tmp{$tmp};
				}
				my %bytes_tmp = split(/[:,]/, $bytes);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
					$self-&gt;{"stat_network_$type"}{$net}{$tmp}{bytes} += $bytes_tmp{$tmp};
				}
				my %duration_tmp = split(/[:,]/, $duration);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %duration_tmp) {
					$self-&gt;{"stat_network_$type"}{$net}{$tmp}{duration} += $duration_tmp{$tmp};
				}
			} else {
				print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_network.dat\n";
				print STDERR "$l\n";
				exit 0;
			}
		}
		$dat_file_network-&gt;close();
	}

	#### Read previous user per network statistics
	my $dat_file_netuser = new IO::File;
	if ($dat_file_netuser-&gt;open("$self-&gt;{Output}/$path/stat_netuser.dat")) {
		while (my $l = &lt;$dat_file_netuser&gt;) {
			chomp($l);
			if ($l =~ s/^([^\s]+)\s([^\s]+)\shits=(\d+);bytes=(\d+);duration=(\d+);largest_file_size=(\d+);largest_file_url=(.*)$//) {
				my $net = $1;
				my $id = $2;
				$self-&gt;{"stat_netuser_$type"}{$net}{$id}{hits} += $3;
				$self-&gt;{"stat_netuser_$type"}{$net}{$id}{bytes} += $4;
				$self-&gt;{"stat_netuser_$type"}{$net}{$id}{duration} += $5;
				if ($6 &gt; $self-&gt;{"stat_netuser_$type"}{$net}{$id}{largest_file_size}) {
					$self-&gt;{"stat_netuser_$type"}{$net}{$id}{largest_file_size} = $6;
					$self-&gt;{"stat_netuser_$type"}{$net}{$id}{largest_file_url} = $7;
				}
			} else {
				print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_netuser.dat\n";
				print STDERR "$l\n";
				exit 0;
			}
		}
		$dat_file_netuser-&gt;close();
	}

	#### Read previous cache statistics
	my $dat_file_code = new IO::File;
	if ($dat_file_code-&gt;open("$self-&gt;{Output}/$path/stat_code.dat")) {
		while (my $l = &lt;$dat_file_code&gt;) {
			chomp($l);
			if ($l =~ s/^([^\s]+)\shits_$type=([^;]+);bytes_$type=([^;]+)$//) {
				my $code = $1;
				my $hits = $2 || '';
				my $bytes = $3 || '';
				$hits =~ s/,$//;	
				$bytes =~ s/,$//;	
				my %hits_tmp = split(/[:,]/, $hits);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
					$self-&gt;{"stat_code_$type"}{$code}{$tmp}{hits} += $hits_tmp{$tmp};
				}
				my %bytes_tmp = split(/[:,]/, $bytes);
				foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
					$self-&gt;{"stat_code_$type"}{$code}{$tmp}{bytes} += $bytes_tmp{$tmp};
				}
			} else {
				print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_code.dat\n";
				print STDERR "$l\n";
				exit 0;
			}
		}
		$dat_file_code-&gt;close();
	}

	#### Read previous mime statistics
	my $dat_file_mime_type = new IO::File;
	if ($dat_file_mime_type-&gt;open("$self-&gt;{Output}/$path/stat_mime_type.dat")) {
		while (my $l = &lt;$dat_file_mime_type&gt;) {
			chomp($l);
			if ($l =~ s/^([^\s]+)\shits=(\d+);bytes=(\d+)//) {
				my $mime = $1;
				$self-&gt;{"stat_mime_type_$type"}{$mime}{hits} += $2;
				$self-&gt;{"stat_mime_type_$type"}{$mime}{bytes} += $3;
			} else {
				print STDERR "ERROR: bad line format found into $self-&gt;{Output}/$path/stat_mime_type.dat\n";
				print STDERR "$l\n";
				exit 0;
			}
		}
		$dat_file_mime_type-&gt;close();
	}

}

sub _print_header
{
	my ($self, $fileout, $str) = @_;

	my $now = strftime("%c", localtime);

	print $$fileout qq{
&lt;!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN"&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta NAME="robots" CONTENT="noindex,nofollow"&gt;
&lt;meta HTTP-EQUIV="Pragma" CONTENT="no-cache"&gt;
&lt;meta HTTP-EQUIV="Cache-Control" content="no-cache"&gt;
&lt;meta HTTP-EQUIV="Expires" CONTENT="$now"&gt;
&lt;meta HTTP-EQUIV="Generator" CONTENT="SquidAnalyzer $VERSION"&gt;
&lt;meta HTTP-EQUIV="Date" CONTENT="$now"&gt;
&lt;meta HTTP-EQUIV="Content-Type" CONTENT="text/html"&gt;
&lt;title&gt;SquidAnalyzer $VERSION Report&lt;/title&gt;
&lt;/head&gt;
&lt;body text="black" bgcolor="white"&gt;
};
	if ($self-&gt;{HeaderFile}) {
		print $$fileout qq{ $self-&gt;{HeaderFile} };
	} else {
		print $$fileout qq{
&lt;style&gt;
body {font-family:arial,helvetica,verdana;font-size:10pt}
td {font-family:arial,helvetica,verdana;font-size:10pt;color:black}
th {font-family:arial,helvetica,verdana;font-weight:bold;font-size:12pt;color:green}
.prog {font-family:arial,helvetica,verdana;font-weight:bold;font-size:24pt;color:green}
&lt;/style&gt;
};
	}
	print $$fileout qq{
&lt;table align=center width="100%"&gt;
&lt;tr&gt;&lt;td align=left&gt;
&lt;font class="prog"&gt;SquidAnalyzer $VERSION&lt;/font&gt;&lt;br&gt;
&lt;i&gt;&lt;b&gt;$Translate{'Generation'} $now.&lt;/b&gt;&lt;/i&gt;
&lt;/td&gt;
&lt;td align=right&gt;
$str
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
};

}


sub _print_footer
{
	my ($self, $fileout) = @_;

	if ($self-&gt;{FooterFile}) {
		print $$fileout qq{ $self-&gt;{FooterFile} };
	} else {
		print $$fileout qq{
&lt;hr&gt;
This file was generated by &lt;a href="http://www.samse.fr/GPL/SquidAnalyzer/"&gt;SquidAnalyzer v$VERSION&lt;/a&gt;
&lt;p&gt;
&lt;b&gt;Author:&lt;/b&gt; $AUTHOR&lt;br&gt;
&lt;b&gt;$COPYRIGHT&lt;/b&gt;
&lt;pre&gt;
This program is provided WITHOUT WARRANTY of any kind, either
expressed or implied. It is free software, and you are welcome
to re-distribute it under same terms of Perl itself.
&lt;/pre&gt;
};
	}
	print $$fileout qq{
&lt;/body&gt;
&lt;/html&gt;
};

}

sub buildHTML
{
	my ($self, $outdir) = @_;

	$outdir ||= $self-&gt;{Output};

	print STDERR "Building HTML output into $outdir\n" if (!$self-&gt;{QuietMode});

	# Load history data for incremental scan
	my $old_year = '';
	my $old_month = '';
	my $old_day = '';
	if ($self-&gt;{history_time}) {
		$old_year = (localtime($self-&gt;{history_time}))[5]+1900;
		$old_month = (localtime($self-&gt;{history_time}))[4]+1;
		$old_month = "0$old_month" if ($old_month &lt; 10);
		$old_day = (localtime($self-&gt;{history_time}))[3];
		$old_day = "0$old_day" if ($old_day &lt; 10);
	}

	# Generate all HTML output
	opendir(DIR, $outdir) || die "Error: can't opendir $outdir: $!";
	my @years = grep { /^\d{4}$/ &amp;&amp; -d "$outdir/$_"} readdir(DIR);
	closedir DIR;
	foreach my $y (@years) {
		next if ($y &lt; $old_year);
		opendir(DIR, "$outdir/$y") || die "Error: can't opendir $outdir/$y: $!";
		my @months = grep { /^\d{2}$/ &amp;&amp; -d "$outdir/$y/$_"} readdir(DIR);
		closedir DIR;
		foreach my $m (@months) {
			next if ($m &lt; $old_month);
			opendir(DIR, "$outdir/$y/$m") || die "Error: can't opendir $outdir/$y/$m: $!";
			my @days = grep { /^\d{2}$/ &amp;&amp; -d "$outdir/$y/$m/$_"} readdir(DIR);
			closedir DIR;
			foreach my $d (@days) {
				next if ($d &lt; $old_day);
				print STDERR "Generating daily statistics for day $y-$m-$d\n" if (!$self-&gt;{QuietMode});
				$self-&gt;gen_html_output($outdir, $y, $m, $d);
			}
			print STDERR "Generating monthly statistics for month $y-$m\n" if (!$self-&gt;{QuietMode});
			$self-&gt;gen_html_output($outdir, $y, $m);
		}
		print STDERR "Generating yearly statistics for year $y\n" if (!$self-&gt;{QuietMode});
		$self-&gt;gen_html_output($outdir, $y);
	}

	$self-&gt;_gen_summary($outdir);
}

sub gen_html_output
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $dir = "$outdir";
	if ($year) {
		$dir .= "/$year";
	}
	if ($month) {
		$dir .= "/$month";
	}
	if ($day) {
		$dir .= "/$day";
	}
	my $stat_date = $self-&gt;set_date($year, $month, $day);

	print STDERR "\tUser statistics...\n" if (!$self-&gt;{QuietMode});
	my $nuser = $self-&gt;_print_user_stat($dir, $year, $month, $day);
	print STDERR "\tMime type statistics...\n" if (!$self-&gt;{QuietMode});
	$self-&gt;_print_mime_stat($dir, $year, $month, $day);
	print STDERR "\tNetwork statistics...\n" if (!$self-&gt;{QuietMode});
	$self-&gt;_print_network_stat($dir, $year, $month, $day);
	my $nurl = 0;
	my $ndomain = 0;
	if ($self-&gt;{UrlReport}) {
		print STDERR "\tTop URL statistics...\n" if (!$self-&gt;{QuietMode});
		$nurl = $self-&gt;_print_top_url_stat($dir, $year, $month, $day);
		print STDERR "\tTop domain statistics...\n" if (!$self-&gt;{QuietMode});
		$ndomain = $self-&gt;_print_top_domain_stat($dir, $year, $month, $day);
	}
	print STDERR "\tCache statistics...\n" if (!$self-&gt;{QuietMode});
	$self-&gt;_print_cache_stat($dir, $year, $month, $day, $nuser, $nurl, $ndomain);

	return ($nuser, $nurl, $ndomain);

}

sub parse_duration
{
	my ($secondes) = @_;

	my $hours = int($secondes/3600);
	$hours = "0$hours" if ($hours &lt; 10);
	$secondes = $secondes - ($hours*3600);
	my $minutes = int($secondes/60);
	$minutes = "0$minutes" if ($minutes &lt; 10);
	$secondes = $secondes - ($minutes*60);
	$secondes = "0$secondes" if ($secondes &lt; 10);

	return "$hours:$minutes:$secondes";
}

sub _print_cache_stat
{
	my ($self, $outdir, $year, $month, $day, $nuser, $nurl, $ndomain) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_code.dat") || return;
	my %code_stat = ();
	my %detail_code_stat = ();
	while (my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($code, $data) = split(/\s/, $l);
		$data =~ /hits_$type=([^;]+);bytes_$type=([^;]+)/;
		my $hits = $1 || '';
		my $bytes = $2 || '';
		$hits =~ s/,$//;
		$bytes =~ s/,$//;
		my %hits_tmp = split(/[:,]/, $hits);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
			$detail_code_stat{$code}{$tmp}{request} = $hits_tmp{$tmp};
			$code_stat{$code}{request} += $hits_tmp{$tmp};
		}
		my %bytes_tmp = split(/[:,]/, $bytes);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
			$detail_code_stat{$code}{$tmp}{bytes} = $bytes_tmp{$tmp};
			$code_stat{$code}{bytes} += $bytes_tmp{$tmp};
		}
	}
	$infile-&gt;close();
	my $total_request =  $code_stat{HIT}{request} + $code_stat{MISS}{request};
	my $total_bytes = $code_stat{HIT}{bytes} + $code_stat{MISS}{bytes};

	my $menu = qq{
&lt;table cellpadding=0 cellspacing=2&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="mime_type.html"&gt;[ $Translate{'Mime_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="network.html"&gt;[ $Translate{'Network_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="user.html"&gt;[ $Translate{'User_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;table cellpadding=0 cellspacing=2&gt;
&lt;tr align="center"&gt;};
	if ($self-&gt;{UrlReport}) {
		$menu .= qq{
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="url.html"&gt;[ $Translate{'Top_url_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="domain.html"&gt;[ $Translate{'Top_domain_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;};
	}
	$menu .= qq{
&lt;th&gt;&lt;font style="font-weight: bold; font-size: 12px;"&gt;&lt;a href="../index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/font&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	my $file = $outdir . '/index.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out, $menu);
	print $out "&lt;h3&gt;$Translate{'Cache_title'} $stat_date&lt;/h3&gt;\n";
	if ($type eq 'day') {
		print $out "&lt;table align=right&gt;&lt;tr bgcolor=\"green\"&gt;&lt;td colspan=8 align=center&gt;&lt;font color=\"white\"&gt;&lt;b&gt;$stat_date&lt;/b&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;\n";
		for my $i ('01' .. '32') {
			print $out "&lt;tr align=center&gt;" if (grep(/^$i$/, '01', '09', '17','25'));
			if ($i == 32) {
				print $out "&lt;td&gt;&amp;nbsp;&lt;/td&gt;";
			} elsif (-f "$outdir/$i/index.html") {
				print $out "&lt;td&gt;&lt;a href=\"$i/index.html\"&gt;$i&lt;/a&gt;&lt;/td&gt;";
			} else {
				print $out "&lt;td&gt;$i&lt;/td&gt;";
			}
			print $out "&lt;/tr&gt;\n" if (grep(/^$i$/, '08', '16', '24', '32'));
		}
		print $out "&lt;/table&gt;\n";
	} elsif ($type eq 'month') {
		print $out "&lt;table align=right&gt;&lt;tr bgcolor=\"green\"&gt;&lt;td colspan=8 align=center&gt;&lt;font color=\"white\"&gt;&lt;b&gt;$stat_date&lt;/b&gt;&lt;/font&gt;&lt;/td&gt;&lt;/tr&gt;\n";
		for my $i ('01' .. '12') {
			print $out "&lt;tr align=center&gt;" if (grep(/^$i$/, '01', '05', '09'));
			if (-f "$outdir/$i/index.html") {
				print $out "&lt;td&gt;&lt;a href=\"$i/index.html\"&gt;$Translate{$i}&lt;/a&gt;&lt;/td&gt;";
			} else {
				print $out "&lt;td&gt;$Translate{$i}&lt;/td&gt;";
			}
			print $out "&lt;/tr&gt;\n" if (grep(/^$i$/, '04', '08', '12'));
		}
		print $out "&lt;/table&gt;\n";
	}
	my $total_cost = sprintf("%2.2f", int($total_bytes/1000000) * $self-&gt;{CostPrice});
	print $out qq{
&lt;table cellpadding=1 cellspacing=3 align=center width=500&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th colspan=2&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th colspan=2&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th colspan=6&gt;$Translate{'Total'}&lt;/th&gt;
&lt;/tr&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Hit'}&lt;/th&gt;
&lt;th&gt;$Translate{'Miss'}&lt;/th&gt;
&lt;th&gt;$Translate{'Hit'}&lt;/th&gt;
&lt;th&gt;$Translate{'Miss'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Users'}&lt;/th&gt;
&lt;th&gt;$Translate{'Sites'}&lt;/th&gt;
&lt;th&gt;$Translate{'Domains'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
&lt;tr bgcolor="beige" align="center"&gt;
&lt;td&gt;$code_stat{HIT}{request}&lt;/td&gt;
&lt;td&gt;$code_stat{MISS}{request}&lt;/td&gt;
&lt;td&gt;$code_stat{HIT}{bytes}&lt;/td&gt;
&lt;td&gt;$code_stat{MISS}{bytes}&lt;/td&gt;
&lt;td&gt;$total_request&lt;/td&gt;
&lt;td&gt;$total_bytes&lt;/td&gt;
&lt;td&gt;$nuser&lt;/td&gt;
&lt;td&gt;$nurl&lt;/td&gt;
&lt;td&gt;$ndomain&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;pre&gt;
	&lt;b&gt;$Translate{'Hit'}:&lt;/b&gt; $Translate{'Hit_help'}
	&lt;b&gt;$Translate{'Miss'}:&lt;/b&gt; $Translate{'Miss_help'}
	&lt;b&gt;$Translate{'Users'}:&lt;/b&gt; $Translate{'Users_help'}
	&lt;b&gt;$Translate{'Sites'}:&lt;/b&gt; $Translate{'Sites_help'}
	&lt;b&gt;$Translate{'Domains'}:&lt;/b&gt; $Translate{'Domains_help'}
	&lt;b&gt;$Translate{'Cost'}:&lt;/b&gt; $Translate{'Cost_help'} $self-&gt;{CostPrice} $self-&gt;{Currency}
&lt;/pre&gt;
&lt;p align=center&gt;&lt;img src=code_request.png&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src=code_bytes.png&gt;&lt;/p&gt;
};

	print $out qq{
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="mime_type.html"&gt;[ $Translate{'Mime_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="network.html"&gt;[ $Translate{'Network_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="user.html"&gt;[ $Translate{'User_link'} ]&lt;/a&gt;&lt;/th&gt;};
	if ($self-&gt;{UrlReport}) {
		print $out qq{
&lt;th&gt;&lt;a href="url.html"&gt;[ $Translate{'Top_url_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;th&gt;&lt;a href="domain.html"&gt;[ $Translate{'Top_domain_link'} ]&lt;/a&gt;&lt;/th&gt;};
	}
	print $out qq{
&lt;th&gt;&lt;a href="../index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	%code_stat = ();
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

	my $last = '23';
	my $first = '00';
	my $title = $Translate{'Hourly'} || 'Hourly';
	my $unit = $Translate{'Hours'} || 'Hours';
	if ($type eq 'day') {
		$last = '31';
		$first = '01';
		$title = $Translate{'Daily'} || 'Daily';
		$unit = $Translate{'Days'} || 'Days';
	} elsif ($type eq 'month') {
		$last = '12';
		$first = '01';
		$title = $Translate{'Monthly'} || 'Monthly';
		$unit = $Translate{'Months'} || 'Months';
	}
	my @labels = ();
	my @hit = ();
	my @miss = ();
	my @total = ();
	for ("$first" .. "$last") {
		push(@labels, "$_");
		my $tot = 0;
		if (exists $detail_code_stat{HIT}{$_}{request}) {
			push(@hit, $detail_code_stat{HIT}{$_}{request});
			$tot += $detail_code_stat{HIT}{$_}{request};
		} else {
			push(@hit, 0);
		}
		if (exists $detail_code_stat{MISS}{$_}{request}) {
			push(@miss, $detail_code_stat{MISS}{$_}{request});
			$tot += $detail_code_stat{MISS}{$_}{request};
		} else {
			push(@miss, 0);
		}
		push(@total, $tot);
		delete $detail_code_stat{HIT}{$_}{request};
		delete $detail_code_stat{MISS}{$_}{request};
	}
	if ($type eq 'month') {
		@labels = (
			$Translate{'01'}, $Translate{'02'}, $Translate{'03'}, $Translate{'04'},
			$Translate{'05'}, $Translate{'06'}, $Translate{'07'}, $Translate{'08'},
			$Translate{'09'}, $Translate{'10'}, $Translate{'11'}, $Translate{'12'}
		);
	}
	my $graph = new GD::Graph::bars3d(600, 300);
	my @data = (
	    [@labels],
	    [@hit],
	    [@miss],
	    [@total]
	);
	my $t1 = $Translate{'Graph_cache_hit_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Requests_graph'},
		x_labels_vertical =&gt; 1,
	);
	$graph-&gt;set_legend($Translate{'Hit_graph'}, $Translate{'Miss_graph'}, $Translate{'Total_graph'});
	my $gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/code_request.png") or die "Error: can't create file $outdir/code_request.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

	@hit = ();
	@miss = ();
	@total = ();
	for ("$first" .. "$last") {
		my $tot = 0;
		if (exists $detail_code_stat{HIT}{$_}{bytes}) {
			push(@hit, int($detail_code_stat{HIT}{$_}{bytes}/1000000));
			$tot += $detail_code_stat{HIT}{$_}{bytes};
		} else {
			push(@hit, 0);
		}
		if (exists $detail_code_stat{MISS}{$_}{bytes}) {
			push(@miss, int($detail_code_stat{MISS}{$_}{bytes}/1000000));
			$tot += $detail_code_stat{MISS}{$_}{bytes};
		} else {
			push(@miss, 0);
		}
		push(@total, int($tot/1000000));
	}
	%detail_code_stat = ();
	$graph = new GD::Graph::bars3d(600, 300);
	@data = (
	    [@labels],
	    [@hit],
	    [@miss],
	    [@total]
	);
	$t1 = $Translate{'Graph_cache_byte_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Megabytes_graph'},
		x_labels_vertical =&gt; 1,
	);
	$graph-&gt;set_legend($Translate{'Hit_graph'}, $Translate{'Miss_graph'}, $Translate{'Total_graph'});
	$gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/code_bytes.png") or die "Error: can't create file $outdir/code_bytes.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

}


sub _print_mime_stat
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_mime_type.dat") || return;
	my %mime_stat = ();
	my $total_count = 0;
	my $total_bytes = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($code, $data) = split(/\s/, $l);
		$data =~ /hits=(\d+);bytes=(\d+)/;
		$mime_stat{$code}{hits} = $1;
		$mime_stat{$code}{bytes} = $2;
		$total_count += $1;
		$total_bytes += $2;
	}
	$infile-&gt;close();

	my $ntype = scalar keys %mime_stat;

	my $file = $outdir . '/mime_type.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	print $out "&lt;h3&gt;$Translate{'Mime_title'} $stat_date&lt;/h3&gt;\n";
	print $out "&lt;b&gt;$Translate{'Mime_number'}:&lt;/b&gt; $ntype&lt;br&gt;\n";
	print $out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th nowrap&gt;$Translate{'Mime_link'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
};
	foreach (sort { $mime_stat{$b}{"$self-&gt;{OrderMime}"} &lt;=&gt; $mime_stat{$a}{"$self-&gt;{OrderMime}"} } keys %mime_stat) {
		my $c_percent = '0.0';
		$c_percent = sprintf("%2.2f", ($mime_stat{$_}{hits}/$total_count) * 100) if ($total_count);
		my $b_percent = '0.0';
		$b_percent = sprintf("%2.2f", ($mime_stat{$_}{bytes}/$total_bytes) * 100) if ($total_bytes);
		my $total_cost = sprintf("%2.2f", int($mime_stat{$_}{bytes}/1000000) * $self-&gt;{CostPrice});
		print $out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left&gt;$_&lt;/td&gt;
&lt;td&gt;$mime_stat{$_}{hits}&lt;/td&gt;
&lt;td&gt;$c_percent&lt;/td&gt;
&lt;td&gt;$mime_stat{$_}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;};
	}
	print $out qq{&lt;/table&gt;};

	print $out qq{
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

}

sub _print_network_stat
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_network.dat") || return;
	my %network_stat = ();
	my %detail_network_stat = ();
	my %total_net_detail = ();
	my $total_hit = 0;
	my $total_bytes = 0;
	my $total_duration = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($network, $data) = split(/\s/, $l);
		$data =~ /hits_$type=([^;]+);bytes_$type=([^;]+);duration_$type=([^;]+);largest_file_size=(\d+);largest_file_url=(.*)/;
		my $hits = $1 || '';
		my $bytes = $2 || '';
		my $duration = $3 || '';
		$network_stat{$network}{largest_file} = $4;
		$network_stat{$network}{url} = $5;
		$hits =~ s/,$//;	
		$bytes =~ s/,$//;	
		$duration =~ s/,$//;	
		my %hits_tmp = split(/[:,]/, $hits);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
			$detail_network_stat{$network}{$tmp}{hits} = $hits_tmp{$tmp};
			$total_net_detail{$tmp}{hits} += $hits_tmp{$tmp};
			$network_stat{$network}{hits} += $hits_tmp{$tmp};
			$total_hit += $hits_tmp{$tmp};
		}
		my %bytes_tmp = split(/[:,]/, $bytes);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
			$detail_network_stat{$network}{$tmp}{bytes} = $bytes_tmp{$tmp};
			$total_net_detail{$tmp}{bytes} += $bytes_tmp{$tmp};
			$network_stat{$network}{bytes} += $bytes_tmp{$tmp};
			$total_bytes += $bytes_tmp{$tmp};
		}
		my %duration_tmp = split(/[:,]/, $duration);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %duration_tmp) {
			$detail_network_stat{$network}{$tmp}{duration} = $duration_tmp{$tmp};
			$total_net_detail{$tmp}{duration} += $duration_tmp{$tmp};
			$network_stat{$network}{duration} += $duration_tmp{$tmp};
			$total_duration += $duration_tmp{$tmp};
		}
	}
	$infile-&gt;close();
	my $nnet = scalar keys %network_stat;

	my $file = $outdir . '/network.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	print $out "&lt;h3&gt;$Translate{'Network_title'} $stat_date&lt;/h3&gt;\n";
	print $out "&lt;b&gt;$Translate{'Network_number'}:&lt;/b&gt; $nnet&lt;br&gt;\n";
	print $out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Network_link'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Users'}&lt;/th&gt;
&lt;th&gt;$Translate{'Largest'}&lt;/th&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;/tr&gt;
};
	if (!-d "$outdir/networks") {
		mkdir("$outdir/networks", 0755) || return;
	}
	my $last = '23';
	my $first = '00';
	my $title = $Translate{'Hourly'} || 'Hourly';
	my $unit = $Translate{'Hours'} || 'Hours';
	if ($type eq 'day') {
		$last = '31';
		$first = '01';
		$title = $Translate{'Daily'} || 'Daily';
		$unit = $Translate{'Days'} || 'Days';
	} elsif ($type eq 'month') {
		$last = '12';
		$first = '01';
		$title = $Translate{'Monthly'} || 'Monthly';
		$unit = $Translate{'Months'} || 'Months';
	}
	foreach my $net (sort { $network_stat{$b}{"$self-&gt;{OrderNetwork}"} &lt;=&gt; $network_stat{$a}{"$self-&gt;{OrderNetwork}"} } keys %network_stat) {
		my $h_percent = '0.0';
		$h_percent = sprintf("%2.2f", ($network_stat{$net}{hits}/$total_hit) * 100) if ($total_hit);
		my $b_percent = '0.0';
		$b_percent = sprintf("%2.2f", ($network_stat{$net}{bytes}/$total_bytes) * 100) if ($total_bytes);
		my $d_percent = '0.0';
		$d_percent = sprintf("%2.2f", ($network_stat{$net}{duration}/$total_duration) * 100) if ($total_duration);
		$network_stat{$net}{duration} = &amp;parse_duration(int($network_stat{$net}{duration}/1000));
		my $total_cost = sprintf("%2.2f", int($network_stat{$net}{bytes}/1000000) * $self-&gt;{CostPrice});
		my $show = "$net.0";
		if (exists $self-&gt;{NetworkAlias}-&gt;{$show}) {
			$show = $self-&gt;{NetworkAlias}-&gt;{$show};
		}
		print $out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left nowrap&gt;&lt;a href="networks/$net/$net.html"&gt;$show&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$network_stat{$net}{hits}&lt;/td&gt;
&lt;td&gt;$h_percent&lt;/td&gt;
&lt;td&gt;$network_stat{$net}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$network_stat{$net}{duration}&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;};

		if (!-d "$outdir/networks/$net") {
			mkdir("$outdir/networks/$net", 0755) || return;
		}
		my $outnet = new IO::File;
		$outnet-&gt;open("&gt;$outdir/networks/$net/$net.html") || return;
		$self-&gt;_print_header(\$outnet);
		print $outnet "&lt;h3&gt;$Translate{'Network_title'} $show - $stat_date&lt;/h3&gt;\n";
		my $retuser = $self-&gt;_print_netuser_stat($outdir, \$outnet, $net);
		print $out qq{
&lt;td&gt;$retuser&lt;/td&gt;
&lt;td&gt;$network_stat{$net}{largest_file}&lt;/td&gt;
&lt;td align=left&gt;$network_stat{$net}{url}&lt;/td&gt;
&lt;/tr&gt;};
		print $outnet qq{
&lt;p align=center&gt;&lt;img src=network_hits.png&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src=network_bytes.png&gt;&lt;/p&gt;
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="../../network.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
		$self-&gt;_print_footer(\$outnet);
		$outnet-&gt;close();

		my @labels = ();
		my @hits = ();
		my @bytes = ();
		for ("$first" .. "$last") {
			push(@labels, "$_");
			if (exists $detail_network_stat{$net}{$_}{hits}) {
				push(@hits, $detail_network_stat{$net}{$_}{hits});
			} else {
				push(@hits, 0);
			}
			if (exists $detail_network_stat{$net}{$_}{bytes}) {
				push(@bytes, int($detail_network_stat{$net}{$_}{bytes}/1000000));
			} else {
				push(@bytes, 0);
			}
		}
		delete $detail_network_stat{$net};
		if ($type eq 'month') {
			@labels = (
				$Translate{'01'}, $Translate{'02'}, $Translate{'03'}, $Translate{'04'},
				$Translate{'05'}, $Translate{'06'}, $Translate{'07'}, $Translate{'08'},
				$Translate{'09'}, $Translate{'10'}, $Translate{'11'}, $Translate{'12'}
			);
		}

		my $graph = new GD::Graph::bars3d(600, 300);
		my @data = (
		    [@labels],
		    [@hits]
		);
		my $t1 = $Translate{'Graph_cache_hit_title'};
		$t1 =~ s/\%s/$title $show/;
		$graph-&gt;set(
			title =&gt; "$t1 $stat_date",
			x_label =&gt; $unit,
			y_label =&gt; $Translate{'Requests_graph'},
			x_labels_vertical =&gt; 1,
		);
		my $gd = $graph-&gt;plot(\@data);
		local (*IMG) = undef;
		open(IMG, "&gt;$outdir/networks/$net/network_hits.png") or die "Error: can't create file $outdir/networks/$net/network_hits.png, $!\n";
		binmode IMG;
		print IMG $gd-&gt;png;
		close(IMG);

		$graph = new GD::Graph::bars3d(600, 300);
		@data = (
		    [@labels],
		    [@bytes]
		);
		$t1 = $Translate{'Graph_cache_byte_title'};
		$t1 =~ s/\%s/$title $show/;
		$graph-&gt;set(
			title =&gt; "$t1 $stat_date",
			x_label =&gt; $unit,
			y_label =&gt; $Translate{'Megabytes_graph'},
			x_labels_vertical =&gt; 1,
		);
		$gd = $graph-&gt;plot(\@data);
		local (*IMG) = undef;
		open(IMG, "&gt;$outdir/networks/$net/network_bytes.png") or die "Error: can't create file $outdir/networks/$net/network_bytes.png, $!\n";
		binmode IMG;
		print IMG $gd-&gt;png;
		close(IMG);
		delete $network_stat{$net};
	}
	print $out "&lt;/table&gt;\n";

	print $out qq{
&lt;p align=center&gt;&lt;img src=network_hits.png&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src=network_bytes.png&gt;&lt;/p&gt;
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

	my @labels = ();
	my @hits = ();
	my @bytes = ();
	for ("$first" .. "$last") {
		push(@labels, "$_");
		if (exists $total_net_detail{$_}{hits}) {
			push(@hits, $total_net_detail{$_}{hits});
		} else {
			push(@hits, 0);
		}
		if (exists $total_net_detail{$_}{bytes}) {
			push(@bytes, int($total_net_detail{$_}{bytes}/1000000));
		} else {
			push(@bytes, 0);
		}
	}
	%total_net_detail = ();
	if ($type eq 'month') {
		@labels = (
			$Translate{'01'}, $Translate{'02'}, $Translate{'03'}, $Translate{'04'},
			$Translate{'05'}, $Translate{'06'}, $Translate{'07'}, $Translate{'08'},
			$Translate{'09'}, $Translate{'10'}, $Translate{'11'}, $Translate{'12'}
		);
	}
	my $graph = new GD::Graph::bars3d(600, 300);
	my @data = (
	    [@labels],
	    [@hits]
	);
	my $t1 = $Translate{'Graph_cache_hit_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Requests_graph'} || 'Requests',
		x_labels_vertical =&gt; 1,
	);
	my $gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/network_hits.png") or die "Error: can't create file $outdir/network_hits.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

	$graph = new GD::Graph::bars3d(600, 300);
	@data = (
	    [@labels],
	    [@bytes]
	);
	$t1 = $Translate{'Graph_cache_byte_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Megabytes_graph'} || 'Mega bytes',
		x_labels_vertical =&gt; 1,
	);
	$gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/network_bytes.png") or die "Error: can't create file $outdir/network_bytes.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

}

sub _print_user_stat
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_user.dat") || return;
	my %user_stat = ();
	my %detail_user_stat = ();
	my %total_user_detail = ();
        my $total_hit = 0;
        my $total_bytes = 0;
        my $total_duration = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($user, $data) = split(/\s/, $l);
		$data =~ /hits_$type=([^;]+);bytes_$type=([^;]+);duration_$type=([^;]+);largest_file_size=(\d+);largest_file_url=(.*)/;
		my $hits = $1 || '';
		my $bytes = $2 || '';
		my $duration = $3 || '';
		$user_stat{$user}{largest_file} = $4;
		$user_stat{$user}{url} = $5;
		$hits =~ s/,$//;
		$bytes =~ s/,$//;
		$duration =~ s/,$//;
		my %hits_tmp = split(/[:,]/, $hits);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
			$detail_user_stat{$user}{$tmp}{hits} = $hits_tmp{$tmp};
			$total_user_detail{$tmp}{hits} += $hits_tmp{$tmp};
			$user_stat{$user}{hits} += $hits_tmp{$tmp};
			$total_hit += $hits_tmp{$tmp};
		}
		my %bytes_tmp = split(/[:,]/, $bytes);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
			$detail_user_stat{$user}{$tmp}{bytes} = $bytes_tmp{$tmp};
			$total_user_detail{$tmp}{bytes} += $bytes_tmp{$tmp};
			$user_stat{$user}{bytes} += $bytes_tmp{$tmp};
			$total_bytes += $bytes_tmp{$tmp};
		}
		my %duration_tmp = split(/[:,]/, $duration);
		foreach my $tmp (sort {$a &lt;=&gt; $b} keys %duration_tmp) {
			$detail_user_stat{$user}{$tmp}{duration} = $duration_tmp{$tmp};
			$total_user_detail{$tmp}{duration} += $duration_tmp{$tmp};
			$user_stat{$user}{duration} += $duration_tmp{$tmp};
			$total_duration += $duration_tmp{$tmp};
		}
	}
	$infile-&gt;close();
	my $nuser = scalar keys %user_stat;

	my $file = $outdir . '/user.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	print $out "&lt;h3&gt;$Translate{'User_title'} $stat_date&lt;/h3&gt;\n";
	print $out "&lt;b&gt;$Translate{'User_number'}:&lt;/b&gt; $nuser&lt;br&gt;\n";
	print $out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Users'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;th&gt;$Translate{'Largest'}&lt;/th&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;/tr&gt;
};
	if (!-d "$outdir/users") {
		mkdir("$outdir/users", 0755) || return;
	}

	my $last = '23';
	my $first = '00';
	my $title = $Translate{'Hourly'} || 'Hourly';
	my $unit = $Translate{'Hours'} || 'Hours';
	if ($type eq 'day') {
		$last = '31';
		$first = '01';
		$title = $Translate{'Daily'} || 'Daily';
		$unit = $Translate{'Days'} || 'Days';
	} elsif ($type eq 'month') {
		$last = '12';
		$first = '01';
		$title = $Translate{'Monthly'} || 'Monthly';
		$unit = $Translate{'Months'} || 'Months';
	}
	foreach my $usr (sort { $user_stat{$b}{"$self-&gt;{OrderUser}"} &lt;=&gt; $user_stat{$a}{"$self-&gt;{OrderUser}"} } keys %user_stat) {
		my $h_percent = '0.0';
		$h_percent = sprintf("%2.2f", ($user_stat{$usr}{hits}/$total_hit) * 100) if ($total_hit);
		my $b_percent = '0.0';
		$b_percent = sprintf("%2.2f", ($user_stat{$usr}{bytes}/$total_bytes) * 100) if ($total_bytes);
		my $d_percent = '0.0';
		$d_percent = sprintf("%2.2f", ($user_stat{$usr}{duration}/$total_duration) * 100) if ($total_duration);
		$user_stat{$usr}{duration} = &amp;parse_duration(int($user_stat{$usr}{duration}/1000));
		my $total_cost = sprintf("%2.2f", int($user_stat{$usr}{bytes}/1000000) * $self-&gt;{CostPrice});
		my $show = $usr;
		if (exists $self-&gt;{UserAlias}-&gt;{$show}) {
			$show = $self-&gt;{UserAlias}-&gt;{$usr};
		}
		my $url = &amp;escape($usr);
		print $out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left nowrap&gt;&lt;a href="users/$url/$url.html"&gt;$show&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$user_stat{$usr}{hits}&lt;/td&gt;
&lt;td&gt;$h_percent&lt;/td&gt;
&lt;td&gt;$user_stat{$usr}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$user_stat{$usr}{duration}&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;td&gt;$user_stat{$usr}{largest_file}&lt;/td&gt;
&lt;td align=left&gt;$user_stat{$usr}{url}&lt;/td&gt;
&lt;/tr&gt;};

		if (!-d "$outdir/users/$url") {
			mkdir("$outdir/users/$url", 0755) || return;
		}
		my $outusr = new IO::File;
		$outusr-&gt;open("&gt;$outdir/users/$url/$url.html") || return;
		$self-&gt;_print_header(\$outusr);
		print $outusr "&lt;h3&gt;$Translate{'User_title'} $usr - $stat_date&lt;/h3&gt;\n";
		if ($self-&gt;{UrlReport}) {
			$self-&gt;_print_user_detail(\$outusr, $outdir, $usr);
		}
		print $outusr qq{
&lt;p align=center&gt;&lt;img src=user_hits.png&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src=user_bytes.png&gt;&lt;/p&gt;
};
		$self-&gt;_print_footer(\$outusr);
		$outusr-&gt;close();

		my @labels = ();
		my @hits = ();
		my @bytes = ();
		for ("$first" .. "$last") {
			push(@labels, "$_");
			if (exists $detail_user_stat{$usr}{$_}{hits}) {
				push(@hits, $detail_user_stat{$usr}{$_}{hits});
			} else {
				push(@hits, 0);
			}
			if (exists $detail_user_stat{$usr}{$_}{bytes}) {
				push(@bytes, int($detail_user_stat{$usr}{$_}{bytes}/1000000));
			} else {
				push(@bytes, 0);
			}
		}
		delete $detail_user_stat{$usr};
		if ($type eq 'month') {
			@labels = (
				$Translate{'01'}, $Translate{'02'}, $Translate{'03'}, $Translate{'04'},
				$Translate{'05'}, $Translate{'06'}, $Translate{'07'}, $Translate{'08'},
				$Translate{'09'}, $Translate{'10'}, $Translate{'11'}, $Translate{'12'}
			);
		}

		my $graph = new GD::Graph::bars3d(600, 300);
		my @data = (
		    [@labels],
		    [@hits]
		);
		my $t1 = $Translate{'Graph_cache_hit_title'};
		$t1 =~ s/\%s/$title $show/;
		$graph-&gt;set(
			title =&gt; "$t1 $stat_date",
			x_label =&gt; $unit,
			y_label =&gt; $Translate{'Requests_graph'},
			x_labels_vertical =&gt; 1,
		);
		my $gd = $graph-&gt;plot(\@data);
		local (*IMG) = undef;
		open(IMG, "&gt;$outdir/users/$url/user_hits.png") or die "Error: can't create file $outdir/users/$url/user_hits.png, $!\n";
		binmode IMG;
		print IMG $gd-&gt;png;
		close(IMG);

		$graph = new GD::Graph::bars3d(600, 300);
		@data = (
		    [@labels],
		    [@bytes]
		);
		$t1 = $Translate{'Graph_cache_byte_title'};
		$t1 =~ s/\%s/$title $show/;
		$graph-&gt;set(
			title =&gt; "$t1 $stat_date",
			x_label =&gt; $unit,
			y_label =&gt; $Translate{'Megabytes_graph'},
			x_labels_vertical =&gt; 1,
		);
		$gd = $graph-&gt;plot(\@data);
		local (*IMG) = undef;
		open(IMG, "&gt;$outdir/users/$url/user_bytes.png") or die "Error: can't create file $outdir/users/$url/user_bytes.png, $!\n";
		binmode IMG;
		print IMG $gd-&gt;png;
		close(IMG);
		delete $user_stat{$usr};
	}
	print $out qq{&lt;/table&gt;};

	print $out qq{
&lt;p align=center&gt;&lt;img src=user_hits.png&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src=user_bytes.png&gt;&lt;/p&gt;
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

	my @labels = ();
	my @hits = ();
	my @bytes = ();
	for ("$first" .. "$last") {
		push(@labels, "$_");
		if (exists $total_user_detail{$_}{hits}) {
			push(@hits, $total_user_detail{$_}{hits});
		} else {
			push(@hits, 0);
		}
		if (exists $total_user_detail{$_}{bytes}) {
			push(@bytes, int($total_user_detail{$_}{bytes}/1000000));
		} else {
			push(@bytes, 0);
		}
	}
	%total_user_detail = ();
	if ($type eq 'month') {
		@labels = (
			$Translate{'01'}, $Translate{'02'},
			$Translate{'03'}, $Translate{'04'},
			$Translate{'05'}, $Translate{'06'},
			$Translate{'07'}, $Translate{'08'},
			$Translate{'09'}, $Translate{'10'},
			$Translate{'11'}, $Translate{'12'}
		);
	}
	my $graph = new GD::Graph::bars3d(600, 300);
	my @data = (
	    [@labels],
	    [@hits]
	);
	my $t1 = $Translate{'Graph_cache_hit_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Requests_graph'},
		x_labels_vertical =&gt; 1,
	);
	my $gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/user_hits.png") or die "Error: can't create file $outdir/user_hits.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

	$graph = new GD::Graph::bars3d(600, 300);
	@data = (
	    [@labels],
	    [@bytes]
	);
	$t1 = $Translate{'Graph_cache_byte_title'};
	$t1 =~ s/\%s/$title/;
	$graph-&gt;set(
		title =&gt; "$t1 $stat_date",
		x_label =&gt; $unit,
		y_label =&gt; $Translate{'Megabytes_graph'},
		x_labels_vertical =&gt; 1,
	);
	$gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/user_bytes.png") or die "Error: can't create file $outdir/user_bytes.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

	return $nuser;
}

sub _print_netuser_stat
{
	my ($self, $outdir, $out, $usrnet) = @_;

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_netuser.dat") || return;
	my %netuser_stat = ();
	my $total_hit = 0;
	my $total_bytes = 0;
	my $total_duration = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($network, $user, $data) = split(/\s/, $l);
		next if ($network ne $usrnet);
		$data =~ /hits=(\d+);bytes=(\d+);duration=(\d+);largest_file_size=(\d+);largest_file_url=(.*)/;
		$netuser_stat{$user}{hits} = $1;
		$netuser_stat{$user}{bytes} = $2;
		$netuser_stat{$user}{duration} = $3;
		$netuser_stat{$user}{largest_file} = $4;
		$total_hit += $1;
		$total_bytes += $2;
		$total_duration += $3;
		$netuser_stat{$user}{url} = $5;
	}
	$infile-&gt;close();
	my $nuser = scalar keys %netuser_stat;

	print $$out qq{
&lt;b&gt;$Translate{'User_number'}:&lt;/b&gt; $nuser&lt;br&gt;
};
	print $$out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Users'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;th&gt;$Translate{'Largest'}&lt;/th&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;/tr&gt;
};
	foreach my $usr (sort { $netuser_stat{$b}{"$self-&gt;{OrderUser}"} &lt;=&gt; $netuser_stat{$a}{"$self-&gt;{OrderUser}"} } keys %netuser_stat) {
		my $h_percent = '0.0';
		$h_percent = sprintf("%2.2f", ($netuser_stat{$usr}{hits}/$total_hit) * 100) if ($total_hit);
		my $b_percent = '0.0';
		$b_percent = sprintf("%2.2f", ($netuser_stat{$usr}{bytes}/$total_bytes) * 100) if ($total_bytes);
		my $d_percent = '0.0';
		$d_percent = sprintf("%2.2f", ($netuser_stat{$usr}{duration}/$total_duration) * 100) if ($total_duration);
		$netuser_stat{$usr}{duration} = &amp;parse_duration(int($netuser_stat{$usr}{duration}/1000));
		my $total_cost = sprintf("%2.2f", int($netuser_stat{$usr}{bytes}/1000000) * $self-&gt;{CostPrice});
		my $show = $usr;
		if (exists $self-&gt;{UserAlias}-&gt;{$show}) {
			$show = $self-&gt;{UserAlias}-&gt;{$usr};
		}
		my $url = &amp;escape($usr);
		print $$out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left&gt;&lt;a href="../../users/$url/$url.html"&gt;$show&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$netuser_stat{$usr}{hits}&lt;/td&gt;
&lt;td&gt;$h_percent&lt;/td&gt;
&lt;td&gt;$netuser_stat{$usr}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$netuser_stat{$usr}{duration}&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;td&gt;$netuser_stat{$usr}{largest_file}&lt;/td&gt;
&lt;td align=left&gt;$netuser_stat{$usr}{url}&lt;/td&gt;
&lt;/tr&gt;};
	}
	print $$out qq{
&lt;/table&gt;
};
	return $nuser;
}

sub _print_user_detail
{
	my ($self, $out, $outdir, $usr) = @_;

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_user_url.dat") || return;
	my %url_stat = ();
	my $total_hit = 0;
	my $total_bytes = 0;
	my $total_duration = 0;
	my $ok = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($user, $data) = split(/\s/, $l);
		last if (($user ne $usr) &amp;&amp; $ok);
		next if ($user ne $usr);
		$ok = 1;
		$data =~ /hits=(\d+);bytes=(\d+);duration=(\d+);url=(.*)/;
		$url_stat{$4}{hits} = $1;
		$url_stat{$4}{bytes} = $2;
		$url_stat{$4}{duration} = $3;
		$total_hit += $1;
		$total_bytes += $2;
		$total_duration += $3;
	}
	$infile-&gt;close();
	my $nurl = scalar keys %url_stat;

	print $$out qq{
&lt;b&gt;$Translate{'Url_number'}:&lt;/b&gt; $nurl&lt;br&gt;
};
	print $$out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
};
	
	foreach my $url (sort { $url_stat{$b}{"$self-&gt;{OrderUrl}"} &lt;=&gt; $url_stat{$a}{"$self-&gt;{OrderUrl}"} } keys %url_stat) {
		my $h_percent = '0.0';
		$h_percent = sprintf("%2.2f", ($url_stat{$url}{hits}/$total_hit) * 100) if ($total_hit);
		my $b_percent = '0.0';
		$b_percent = sprintf("%2.2f", ($url_stat{$url}{bytes}/$total_bytes) * 100) if ($total_bytes);
		my $d_percent = '0.0';
		$d_percent = sprintf("%2.2f", ($url_stat{$url}{duration}/$total_duration) * 100) if ($total_duration);
		$url_stat{$url}{duration} = &amp;parse_duration(int($url_stat{$url}{duration}/1000));
		my $total_cost = sprintf("%2.2f", int($url_stat{$url}{bytes}/1000000) * $self-&gt;{CostPrice});
		print $$out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left&gt;&lt;a href="http://$url/" target="_blank"&gt;$url&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$url_stat{$url}{hits}&lt;/td&gt;
&lt;td&gt;$h_percent&lt;/td&gt;
&lt;td&gt;$url_stat{$url}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$url_stat{$url}{duration}&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;};
	}
	print $$out qq{
&lt;/table&gt;
};

}

sub _print_top_url_stat
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_user_url.dat") || return;
	my %url_stat = ();
	my $total_hits = 0;
	my $total_bytes = 0;
	my $total_duration = 0;
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($user, $data) = split(/\s/, $l);
		$data =~ /hits=(\d+);bytes=(\d+);duration=(\d+);url=(.*)/;
		my $url = $4;
		$url_stat{$url}{hits} = $1;
		$url_stat{$url}{bytes} = $2;
		$url_stat{$url}{duration} = $3;
		$total_hits += $1;
		$total_bytes += $2;
		$total_duration += $3;
	}
	$infile-&gt;close();

	my $nurl = scalar keys %url_stat;

	my $file = $outdir . '/url.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	for my $type ('Hits', 'Bytes', 'Duration') {
		my $t1 = $Translate{"Url_${type}_title"};
		$t1 =~ s/\%d/$self-&gt;{TopNumber}/;
		print $out "&lt;h3&gt;$t1 $stat_date&lt;/h3&gt;\n";
		print $out "&lt;b&gt;$Translate{'Url_number'}:&lt;/b&gt; $nurl&lt;br&gt;\n";
		print $out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
};
		my $i = 0;
		foreach (sort { $url_stat{$b}{"\L$type\E"} &lt;=&gt; $url_stat{$a}{"\L$type\E"} } keys %url_stat) {
			my $c_percent = '0.0';
			$c_percent = sprintf("%2.2f", ($url_stat{$_}{hits}/$total_hits) * 100) if ($total_hits);
			my $b_percent = '0.0';
			$b_percent = sprintf("%2.2f", ($url_stat{$_}{bytes}/$total_bytes) * 100) if ($total_bytes);
			my $d_percent = '0.0';
			$d_percent = sprintf("%2.2f", ($url_stat{$_}{duration}/$total_duration) * 100) if ($total_duration);
			my $total_cost = sprintf("%2.2f", int($url_stat{$_}{bytes}/1000000) * $self-&gt;{CostPrice});
			my $duration = &amp;parse_duration(int($url_stat{$_}{duration}/1000));
			print $out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left&gt;&lt;a href="http://$_/" target="_blank"&gt;$_&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$url_stat{$_}{hits}&lt;/td&gt;
&lt;td&gt;$c_percent&lt;/td&gt;
&lt;td&gt;$url_stat{$_}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$duration&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;};
			$i++;
			last if ($i &gt; $self-&gt;{TopNumber});
		}
		print $out qq{&lt;/table&gt;};
	}

	print $out qq{
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

	return $nurl;
}

sub _print_top_domain_stat
{
	my ($self, $outdir, $year, $month, $day) = @_;

	my $stat_date = $self-&gt;set_date($year, $month, $day);

	my $type = 'hour';
	if (!$day) {
		$type = 'day';
	}
	if (!$month) {
		$type = 'month';
	}

	# Load code statistics
	my $infile = new IO::File;
	$infile-&gt;open("$outdir/stat_user_url.dat") || return;
	my %url_stat = ();
	my %domain_stat = ();
	my $total_hits = 0;
	my $total_bytes = 0;
	my $total_duration = 0;
	my %perdomain = ();
	while(my $l = &lt;$infile&gt;) {
		chomp($l);
		my ($user, $data) = split(/\s/, $l);
		$data =~ /hits=(\d+);bytes=(\d+);duration=(\d+);url=(.*)/;
		my $url = $4;
		my $hits = $1;
		my $bytes = $2;
		my $duration = $3;
		$url =~ /(\.[^\.]+)$/;
		my $toplevel = lc($1);
		if (grep(/^$toplevel/, @TopLevelDomain)) {
			if ($url =~ /([^\.]+)(\.[^\.]+)$/) {
				$perdomain{$2}{hits} += $hits;
				$perdomain{$2}{bytes} += $bytes;
				$domain_stat{"$1$2"}{hits} = $hits;
				$domain_stat{"$1$2"}{bytes} = $bytes;
				$domain_stat{"$1$2"}{duration} = $duration;
			}
		} else {
			$perdomain{'other'}{hits} += $hits;
			$perdomain{'other'}{bytes} += $bytes;
			$domain_stat{'unknown'}{hits} = $hits;
			$domain_stat{'unknown'}{bytes} = $bytes;
			$domain_stat{'unknown'}{duration} = $duration;
		}
		$total_hits += $hits;
		$total_bytes += $bytes;
		$total_duration += $duration;
	}
	$infile-&gt;close();

	my $nurl = scalar keys %domain_stat;

	my $file = $outdir . '/domain.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	for my $type ('Hits', 'Bytes', 'Duration') {
		my $t1 = $Translate{"Domain_${type}_title"};
		$t1 =~ s/\%d/$self-&gt;{TopNumber}/;
		print $out "&lt;h3&gt;$t1 $stat_date&lt;/h3&gt;\n";
		print $out "&lt;b&gt;$Translate{'Domain_number'}:&lt;/b&gt; $nurl&lt;br&gt;\n";
		print $out qq{
&lt;table cellpadding=1 cellspacing=1 align=center&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;$Translate{'Url'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Bytes'}&lt;/th&gt;
&lt;th&gt;$Translate{'Duration'}&lt;/th&gt;
&lt;th nowrap&gt;% $Translate{'Time'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
};
		my $i = 0;
		foreach (sort { $domain_stat{$b}{"\L$type\E"} &lt;=&gt; $domain_stat{$a}{"\L$type\E"} } keys %domain_stat) {
			my $c_percent = '0.0';
			$c_percent = sprintf("%2.2f", ($domain_stat{$_}{hits}/$total_hits) * 100) if ($total_hits);
			my $b_percent = '0.0';
			$b_percent = sprintf("%2.2f", ($domain_stat{$_}{bytes}/$total_bytes) * 100) if ($total_bytes);
			my $d_percent = '0.0';
			$d_percent = sprintf("%2.2f", ($domain_stat{$_}{duration}/$total_duration) * 100) if ($total_duration);
			my $total_cost = sprintf("%2.2f", int($domain_stat{$_}{bytes}/1000000) * $self-&gt;{CostPrice});
			my $duration = &amp;parse_duration(int($domain_stat{$_}{duration}/1000));
			print $out qq{
&lt;tr bgcolor="beige" align=right&gt;
&lt;td align=left&gt;*.$_&lt;/td&gt;
&lt;td&gt;$domain_stat{$_}{hits}&lt;/td&gt;
&lt;td&gt;$c_percent&lt;/td&gt;
&lt;td&gt;$domain_stat{$_}{bytes}&lt;/td&gt;
&lt;td&gt;$b_percent&lt;/td&gt;
&lt;td&gt;$duration&lt;/td&gt;
&lt;td&gt;$d_percent&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;};
			$i++;
			last if ($i &gt; $self-&gt;{TopNumber});
		}
		print $out qq{&lt;/table&gt;};
	}

	print $out qq{
&lt;p align=center&gt;&lt;img src="domain_hits.png"&gt;&lt;/p&gt;
&lt;p align=center&gt;&lt;img src="domain_bytes.png"&gt;&lt;/p&gt;
&lt;table cellpadding=5 cellspacing=5 align=center&gt;
&lt;tr align="center"&gt;
&lt;th&gt;&lt;a href="index.html"&gt;[ $Translate{'Back_link'} ]&lt;/a&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/table&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

	my @labels = ();
	my @hits = ();
	my @bytes = ();
	foreach my $dom (keys %perdomain) {
		next if ($dom eq 'other');
		push(@labels, $dom);
		push(@hits, $perdomain{$dom}{hits});
		push(@bytes, int($perdomain{$dom}{bytes}/1000000));
	}
	push(@labels, 'other');
	push(@hits, $perdomain{'other'}{hits});
	push(@bytes, $perdomain{'other'}{bytes});
	my $graph = new GD::Graph::bars3d(600, 300);
	my @data = (
	    [@labels],
	    [@hits]
	);
	$graph-&gt;set(
		title =&gt; $Translate{'Domain_graph_hits_title'} . " $stat_date",
		x_label =&gt; $Translate{'Domains_graph'},
		y_label =&gt; $Translate{'Requests_graph'},
		x_labels_vertical =&gt; 1,
	);
	my $gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/domain_hits.png") or die "Error: can't create file $outdir/domain_hits.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);
	$graph = new GD::Graph::bars3d(600, 300);
	@data = (
	    [@labels],
	    [@bytes]
	);
	$graph-&gt;set(
		title =&gt; $Translate{'Domain_graph_bytes_title'} . " $stat_date",
		x_label =&gt; $Translate{'Domains_graph'},
		y_label =&gt; $Translate{'Megabytes_graph'},
		x_labels_vertical =&gt; 1,
	);
	$gd = $graph-&gt;plot(\@data);
	local (*IMG) = undef;
	open(IMG, "&gt;$outdir/domain_bytes.png") or die "Error: can't create file $outdir/domain_bytes.png, $!\n";
	binmode IMG;
	print IMG $gd-&gt;png;
	close(IMG);

	return $nurl;
}

sub _gen_summary
{
	my ($self, $outdir) = @_;

	# Get all day subdirectory
        opendir(DIR, "$outdir") or die "ERROR: Can't read directory $outdir, $!\n";
        my @dirs = grep { /^\d{4}$/ &amp;&amp; -d "$outdir/$_" } readdir(DIR);
        closedir DIR;

	my %code_stat = ();
	my %total_request =  ();
	my %total_bytes = ();
	foreach my $d (@dirs) {
		# Load code statistics
		my $infile = new IO::File;
		$infile-&gt;open("$outdir/$d/stat_code.dat") || return;
		while(my $l = &lt;$infile&gt;) {
			chomp($l);
			my ($code, $data) = split(/\s/, $l);
			$data =~ /hits_month=([^;]+);bytes_month=(.*)/;
			my $hits = $1 || '';
			my $bytes = $2 || '';
			$hits =~ s/,$//;
			$bytes =~ s/,$//;
			my %hits_tmp = split(/[:,]/, $hits);
			foreach my $tmp (sort {$a &lt;=&gt; $b} keys %hits_tmp) {
				$code_stat{$d}{$code}{request} += $hits_tmp{$tmp};
			}
			my %bytes_tmp = split(/[:,]/, $bytes);
			foreach my $tmp (sort {$a &lt;=&gt; $b} keys %bytes_tmp) {
				$code_stat{$d}{$code}{bytes} += $bytes_tmp{$tmp};
			}
		}
		$infile-&gt;close();
		$total_request{$d} =  $code_stat{$d}{HIT}{request} + $code_stat{$d}{MISS}{request};
		$total_bytes{$d} = $code_stat{$d}{HIT}{bytes} + $code_stat{$d}{MISS}{bytes};
	}
	my $file = $outdir . '/index.html';
	my $out = new IO::File;
	$out-&gt;open("&gt;$file") || die "ERROR: Unable to open $file. $!\n";
	# Print the HTML header
	$self-&gt;_print_header(\$out);
	print $out qq{
&lt;table cellpadding=1 cellspacing=3 align=center width=500&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th colspan=2&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th colspan=2&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th colspan=4&gt;$Translate{'Total'}&lt;/th&gt;
&lt;/tr&gt;
&lt;tr bgcolor="blanchedalmond" align="center"&gt;
&lt;th nowrap&gt;$Translate{'Years'}&lt;/th&gt;
&lt;th&gt;$Translate{'Hit'}&lt;/th&gt;
&lt;th&gt;$Translate{'Miss'}&lt;/th&gt;
&lt;th&gt;$Translate{'Hit'}&lt;/th&gt;
&lt;th&gt;$Translate{'Miss'}&lt;/th&gt;
&lt;th&gt;$Translate{'Requests'}&lt;/th&gt;
&lt;th&gt;$Translate{'Bytes'}&lt;/th&gt;
&lt;th nowrap&gt;$Translate{'Cost'} $self-&gt;{Currency}&lt;/th&gt;
&lt;/tr&gt;
};
	foreach my $year (keys %code_stat) {
		my $total_cost = sprintf("%2.2f", int($total_bytes{$year}/1000000) * $self-&gt;{CostPrice});
		print $out qq{
&lt;tr bgcolor="beige" align="center"&gt;
&lt;td nowrap&gt;&lt;a href="$year/index.html"&gt;$Translate{'Stat_label'} $year&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$code_stat{$year}{HIT}{request}&lt;/td&gt;
&lt;td&gt;$code_stat{$year}{MISS}{request}&lt;/td&gt;
&lt;td&gt;$code_stat{$year}{HIT}{bytes}&lt;/td&gt;
&lt;td&gt;$code_stat{$year}{MISS}{bytes}&lt;/td&gt;
&lt;td&gt;$total_request{$year}&lt;/td&gt;
&lt;td&gt;$total_bytes{$year}&lt;/td&gt;
&lt;td&gt;$total_cost&lt;/td&gt;
&lt;/tr&gt;};
	}
	print $out qq{
&lt;/table&gt;
&lt;pre&gt;
	&lt;b&gt;$Translate{'Hit'}:&lt;/b&gt; $Translate{'Hit_help'}
	&lt;b&gt;$Translate{'Miss'}:&lt;/b&gt; $Translate{'Miss_help'}
	&lt;b&gt;$Translate{'Cost'}:&lt;/b&gt; $Translate{'Cost_help'} $self-&gt;{CostPrice} $self-&gt;{Currency}
&lt;/pre&gt;
};
	$self-&gt;_print_footer(\$out);
	$out-&gt;close();

}

sub parse_config
{
	my ($file) = @_;

	&amp;usage if (!-e $file);

	my %opt = ();
	open(CONF, $file) or die "ERROR: can't open file $file, $!\n";
	while (my $l = &lt;CONF&gt;) {
		chomp($l);
		next if (!$l || ($l =~ /^[\s\t]*#/)); 
		my ($key, $val) = split(/[\s\t]+/, $l);
		$opt{$key} = $val;
	}
	close(CONF);

	# Check config
	if (!exists $opt{Output} || !-d $opt{Output}) {
		print STDERR "Error: you must give a valid output directory. See option: Output\n";
		exit 0;
	}
	if (!exists $opt{LogFile} || !-f $opt{LogFile}) {
		print STDERR "Error: you must give the path to the Squid log file. See option: LogFile\n";
		exit 0;
	}
	if (exists $opt{DateFormat}) {
		if ( ($opt{DateFormat} !~ m#\%y#) || ($opt{DateFormat} !~ m#\%m#) || ($opt{DateFormat} !~ m#\%d#) ) {
			print STDERR "Error: bad date format, must have \%y, \%m, \%d. See option: DateFormat\n";
			exit 0;
		}
	}
	if ($opt{Lang} &amp;&amp; !-e $opt{Lang}) {
		print STDERR "Error: can't find translation file $opt{Lang}. See option: Lang\n";
		exit 0;
	}
	if ($opt{HeaderFile} &amp;&amp; !-e $opt{HeaderFile}) {
		print STDERR "Error: can't find custom header file $opt{HeaderFile}. See option: HeaderFile\n";
		exit 0;
	}
	if ($opt{FooterFile} &amp;&amp; !-e $opt{FooterFile}) {
		print STDERR "Error: can't find custom footer file $opt{FooterFile}. See option: FooterFile\n";
		exit 0;
	}
	return %opt;
}

sub parse_network_aliases
{
	my ($file) = @_;

	return if (!$file || !-f $file);

	my %alias = ();
	open(ALIAS, $file) or die "ERROR: can't open network aliases file $file, $!\n";
	while (my $l = &lt;ALIAS&gt;) {
		chomp($l);
		next if (!$l || ($l =~ /^[\s\t]*#/)); 
		my ($name, $network) = split(/[\t]+/, $l);
		$alias{$network} = $name;
	}
	close(ALIAS);

	return \%alias;
}

sub parse_user_aliases
{
	my ($file) = @_;

	return if (!$file || !-f $file);

	my %alias = ();
	open(ALIAS, $file) or die "ERROR: can't open user aliases file $file, $!\n";
	while (my $l = &lt;ALIAS&gt;) {
		chomp($l);
		next if (!$l || ($l =~ /^[\s\t]*#/)); 
		my ($name, $user) = split(/[\t]+/, $l);
		$alias{$user} = $name;
	}
	close(ALIAS);

	return \%alias;
}

sub usage
{
        print "Usage: squidanalyzer.pl [ squidanalyzer.conf ]\n";

        exit 0;
}

sub parse_user_exclusion
{
	my ($file) = @_;

	return if (!$file || !-f $file);

	my @exclusion = ();
	open(EXCLUDED, $file) or die "ERROR: can't open client exclusion file $file, $!\n";
	while (my $l = &lt;EXCLUDED&gt;) {
		chomp($l);
		next if (!$l || ($l =~ /^[\s\t]*#/)); 
		push(@exclusion, $l);
	}
	close(EXCLUDED);

	return \@exclusion;
}

# User URL-encode
sub escape
{
	my ($toencode) = @_;

	return undef unless defined($toencode);

	$toencode =~ s/[^a-zA-Z0-9_.-]/_/g;

	return $toencode;
}

# Set date to user format
sub set_date
{
	my ($self, $year, $month, $day) = @_;

	my $date_format = $self-&gt;{DateFormat};

        $date_format =~ s/\%y/$year/i;
        $date_format =~ s/\%m/$month/i;
        $date_format =~ s/\%d/$day/i;

        $date_format =~ s/\D{2,3}//;
        $date_format =~ s/^\D+//;
        $date_format =~ s/\D+$//;

        return $date_format;
}

1;

__END__

</pre></body></html>