#!/usr/bin/perl -w

use strict;      # force definations for all vars an subroutines
use warnings;    # force check of unset variables in expressions
use diagnostics; # give var/code line number on faults

use File::Basename; # for basename(), dirname(),
use POSIX qw(strftime); # for strftime(),
use POSIX qw(mktime);
### use Date::Parse; # for str2time(),



my $self=basename(__FILE__);



####################
#####   ARGS   #####
####################
my (%MYARG, $argi);

#$MYARG{'filename'}="/var/log/haproxy/haproxy*.log";
#$MYARG{'greptime'}=time() - 86400;
# 15-Jan-2017 05:20:01.650
#$MYARG{'grepdate'}=strftime("%b %e", localtime( $MYARG{'greptime'} ));
$MYARG{'maxlines'}=25;
$MYARG{'sortrequesttypes'}=0;
$MYARG{'showlongstat'}=0;

$argi=0;
while ($argi<$#ARGV+1) {
	#print("\$ARGV[$argi]=|".$ARGV[$argi]."|\n");
	if ($ARGV[$argi] eq '-l') {
		if ($argi+1<$#ARGV+1) { $MYARG{'filename'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -l areqire argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-d') {
		if ($argi+1<$#ARGV+1) { $MYARG{'grepdate'}=$ARGV[$argi+1]; $argi++; $argi++; next; } ### $MYARG{'greptime'}=str2time($ARGV[$argi+1]);
		else                  { print("$self: option -d areqire argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-u') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxlines'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -u areqire argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '-h') {
		if ($argi+1<$#ARGV+1) { $MYARG{'maxlines'}=$ARGV[$argi+1]; $argi++; $argi++; next; }
		else                  { print("$self: option -h areqire argument\n"); exit(1); }
	}
	if ($ARGV[$argi] eq '--showlongstat') {
		$MYARG{'showlongstat'}=1; $argi++; next;
	}
	if ($ARGV[$argi] eq '--sortrequesttypes') {
		$MYARG{'sortrequesttypes'}=1; $argi++; next;
	}
	if ($ARGV[$argi] eq '--help') {
		&usage();
		exit(0);
	}
	if (substr($ARGV[$argi],0,1) eq '-') {
		print("$self: Unknown argument: '$ARGV[$argi]'\n");
		exit(1);
	}
	#$MYARG{'filename'}=$ARGV[$argi];
	last;
	#print("$self: Unknown option $ARGV[$argi]\n"); exit(1);
}

while ($argi<$#ARGV+1) {
	push(@{$MYARG{'filename'}}, $ARGV[$argi]);
	$argi++;
}



#################
# validate args #
#################
if (!defined($MYARG{'grepdate'})) { &usage(1); exit(); }
if (!defined($MYARG{'filename'})) { &usage(2); exit(); }

my ($YYYY, $mm, $dd, $HH, $MM, $SS);
if ($MYARG{'grepdate'}=~m/^([0-9][0-9][0-9][0-9])[\/\.-]([0-1][0-9]|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[\/\.-]([0-3][0-9])$/) { ($YYYY, $mm, $dd, $HH, $MM, $SS)=($1,$2,$3, 0,0,0); }
if ($MYARG{'grepdate'}=~m/^([0-3][0-9])[\/\.-]([0-1][0-9]|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[\/\.-]([0-9][0-9][0-9][0-9])$/) { ($YYYY, $mm, $dd, $HH, $MM, $SS)=($3,$2,$1, 0,0,0); }
if (defined($YYYY) && defined($mm) && defined($dd)) {
	my %M2D=('Jan'=>1,'Feb'=>2,'Mar'=>3,'Apr'=>4,'May'=>5,'Jun'=>6,'Jul'=>7,'Aug'=>8,'Sep'=>9,'Oct'=>10,'Nov'=>11,'Dec'=>12);
	#print("|($YYYY, $mm, $dd, $HH, $MM, $SS)|\n");
	if (defined($M2D{$mm})) { $mm=$M2D{$mm}; }
	if ($YYYY>1900) { $YYYY=$YYYY-1900; }
	if ($mm>0) { $mm=$mm-1; }
	$MYARG{'greptime'}=mktime( $SS, $MM, $HH, $dd, $mm, $YYYY );
	if (!defined($MYARG{'greptime'})) { print("$self: 1 option -d has invalid argument: '$MYARG{'grepdate'}'\n"); exit(1); }
}

if ($MYARG{'grepdate'} eq 'yesterday') { $MYARG{'greptime'}=time() - 86400; }
if ($MYARG{'grepdate'} eq 'today')     { $MYARG{'greptime'}=time() -     0; }
if (!defined($MYARG{'greptime'})) { print("$self: 2 option -d has invalid argument: '$MYARG{'grepdate'}'\n"); exit(1); }
$MYARG{'grepdate'}=strftime("%b %e", localtime( $MYARG{'greptime'} ));
#print("|$MYARG{'greptime'}| = |$MYARG{'grepdate'}|\n");
#exit();





#########################
#####   INIT DATA   #####
#########################

# GET/POST/OPTIONS/PUT/DELETE

my (%AERRORS,%VERRORS,%HHLIST);
my (%IPADDR,%METH,%AREQ,%VREQ,%VERS,%CODE);

%IPADDR=();
%METH=();
%AREQ=();
%VREQ=();
%CODE=();

for my $ii ( (0 .. 23) ) {
	my $hh=sprintf("%02d",$ii);
	$HHLIST{$hh}{'GET'}    = 0;
	$HHLIST{$hh}{'HEAD'}   = 0;
	$HHLIST{$hh}{'POST'}   = 0;
	$HHLIST{$hh}{'OPTIONS'}= 0;
	$HHLIST{$hh}{'PUT'}    = 0;
	$HHLIST{$hh}{'DELETE'} = 0;
	$HHLIST{$hh}{'TRACE'}  = 0;
	$HHLIST{$hh}{'TRACK'}  = 0;
	$HHLIST{$hh}{'CONNECT'}= 0;
}





###############################
#####   PARSE HTTPD LOG   #####
###############################

foreach my $filename (@{$MYARG{'filename'}}) {
	my ($HTTPD,$name,$line,$date,$YYYY,$mm,$dd,$hh,$ii,$ss);
	my ($sp1,$sp2,$sp3,$sp4,$spc);
	my ($ipaddr,$meth,$areq,$vreq,$vers,$code,$bytes);
	
	if (!open($HTTPD,"<".$filename)) {
		print("Unable open(<'$filename'), Reason: $!\n");
		#exit(1);
		next;
	}
	while ($line=<$HTTPD>) {
		$line=~s,[\r\n]+$,,g;
		#print("line=|$line|\n");
		if (substr($line,0,length($MYARG{'grepdate'})) ne $MYARG{'grepdate'}) { next; }
		#print("line=|$line|\n");
		
		##############
		# access.log #
		##############
		# Feb 22 06:33:14 localhost haproxy[2545]: ::ffff:180.76.15.152:42068 [22/Feb/2017:06:33:14.672] public host___wbcd___aclapp/host___wbcd___aclapp 1/0/0/61/62 200 8246 - - ---- 0/0/0/0/0 0/0 "GET / HTTP/1.1"
		#            ^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-3 ][0-9] ([0-9][0-9]):([0-5][0-9]):([0-5][0-9]) \+[0-9]+\] "([A-Z]+) ([^ ]+) (HTTP\/[0-9\.]+)" ([0-9\-]+)[ ]([0-9\-]+)[ ]?("[^"]+")?[ ]?("[^"]+")?/
		if ($line=~m/^(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) [0-3 ][0-9] [0-2][0-9]:[0-5][0-9]:[0-5][0-9] [A-Za-z0-9_-]+ [A-Za-z0-9_-]+\[[0-9]+\]: (::ffff:)?([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}|[0-9a-f:]+):([0-9]+) \[([0-9][0-9])\/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\/([0-9][0-9][0-9][0-9]):([0-2][0-9]):([0-5][0-9]):([0-5][0-9])\.[0-9][0-9][0-9]\] ([A-Za-z0-9_-]+) ([A-Za-z0-9_-]+)\/<?([A-Za-z0-9_-]+)>? -?[0-9]+\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+ ([0-9]+) ([0-9]+) [^ ]+ [^ ]+ [^ ]+ -?[0-9]+\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+\/-?[0-9]+ [0-9]+\/[0-9]+ "([A-Z]+) ([^ ]+) (HTTP\/[0-9\.]+)"/) {
			#print("line=|$1|$2|$3|$4|$5|$6|$7|$8|$9|$10|$11|$12|$13|$14|$15|$16|$17|$18|\n");
			($ipaddr, $dd,$mm,$YYYY,$hh,$ii,$ss, $name,$code,$bytes, $meth,$areq,$vers)=($3, $5,$6,$7,$8,$9,$10, $13,$14,$15, $16,$17,$18);
			$vreq=$name.':'.$areq;
			#print("ipaddr=$ipaddr $YYYY/$mm/$dd $hh:$ii:$ss name=$name code=$code bytes=$bytes meth=$meth areq=$name:$areq vers=$vers\n");
			if (!defined($HHLIST{$hh}{$meth})) { $HHLIST{$hh}{$meth}=0; print("if (!defined(\$HHLIST{$hh}{$meth}))\n"); }
			$HHLIST{$hh}{$meth} = $HHLIST{$hh}{$meth} + 1;
			if (defined($IPADDR{$ipaddr})) { $IPADDR{$ipaddr}=$IPADDR{$ipaddr}+1; } else { $IPADDR{$ipaddr}=1; }
			if (defined($METH{$meth})) { $METH{$meth}=$METH{$meth}+1; } else { $METH{$meth}=1; }
			if (defined($AREQ{$areq})) { $AREQ{$areq}=$AREQ{$areq}+1; } else { $AREQ{$areq}=1; }
			if (defined($VREQ{$vreq})) { $VREQ{$vreq}=$VREQ{$vreq}+1; } else { $VREQ{$vreq}=1; }
			if (defined($VERS{$vers})) { $VERS{$vers}=$VERS{$vers}+1; } else { $VERS{$vers}=1; }
			if (defined($CODE{$code})) { $CODE{$code}=$CODE{$code}+1; } else { $CODE{$code}=1; }
			if ($code>=400) {
				if (defined($AERRORS{$code}{$areq})) { $AERRORS{$code}{$areq}=$AERRORS{$code}{$areq}+1; } else { $AERRORS{$code}{$areq}=1; }
				if (defined($VERRORS{$code}{$vreq})) { $VERRORS{$code}{$vreq}=$VERRORS{$code}{$vreq}+1; } else { $VERRORS{$code}{$vreq}=1; }
			}
		} #else { print("NOK line=|$line|\n"); }
		
	} # while ($line=<$HTTPD>)
	close($HTTPD);
} # foreach my $filename (@{$MYARG{'filename'}})










#######################
#####   DISPLAY   #####
#######################

#print("HAProxy log summaries for $MYARG{'grepdate'} (".strftime('%Y/%m/%d',localtime($MYARG{'greptime'})).")\n");
#print("\n");
# 
# &show_summary();
# print("\n"); print("\n"); print("\n");
# 
# &show_errors();
# 
# &hour_stat_short();
# print("\n"); print("\n"); print("\n");
# 
# if ($MYARG{'showlongstat'}==1) { &hour_stat_long(); print("\n"); print("\n"); print("\n"); }
# 
&top_stat();
# print("\n"); print("\n"); print("\n");










##########################################################################
# sub show_summary()
# global:  %MYARG, %HHLIST, %IPADDR, %NAMES, %TYPES,
# @param   void
# @return  void
sub show_summary {
	#my ()=@_;
	# @_ = ARGS call function
	my ($key, $ki, $kname);
	
	foreach $ki (sort(keys(%HHLIST))) { $key=$ki; last; }
	
	print("Grand Totals\n");
	print("============\n");
	print("requests: types\n");
	print("--------------\n");
	if ($MYARG{'sortrequesttypes'}==1) {
		my (%HHSS);
		foreach $kname (('GET','HEAD','POST','OPTIONS','PUT','DELETE','TRACE','TRACK','CONNECT')) {
		if (!defined($HHSS{$kname})) { $HHSS{$kname}=0; }
		$HHSS{$kname}=$HHSS{$kname} + &hash_key2_sum(\%HHLIST,$kname);
		}
		foreach $kname (reverse(sort { $HHSS{$a} <=> $HHSS{$b} } keys(%HHSS))) {
		print(sprintf(" % 7d   $kname ",$HHSS{$kname})."\n");
		}
	} else {
		foreach $kname (('GET','HEAD','POST','OPTIONS','PUT','DELETE','TRACE','TRACK','CONNECT')) {
		print(sprintf(" % 7d   $kname",&hash_key2_sum(\%HHLIST,$kname))."\n");
		}
	}
	
	print("\n");
	print(sprintf(" % 7d   requests from unique hosts",&hash_count(\%IPADDR))."\n");
	print(sprintf(" % 7d   requests unique HTTP methods",&hash_count(\%METH))."\n");
	print(sprintf(" % 7d   requests unique HTTP path",&hash_count(\%AREQ))."\n");
	print(sprintf(" % 7d   requests unique HTTP vhosts path",&hash_count(\%VREQ))."\n");
	print(sprintf(" % 7d   requests unique HTTP versions",&hash_count(\%VERS))."\n");
}



##########################################################################
# sub show_errors()
# global:  %AERRORS
# @param   void
# @return  void
sub show_errors {
	#my ()=@_;
	# @_ = ARGS call function
	my ($code, $areq, $vreq);
	my ($info, $newline);
	
	print("Requests with error response codes:\n");
	print("===================================\n");
	
	$newline=0;
	foreach $code (sort(keys(%AERRORS))) {
		$newline=1;
		$info="HTTP $code";
		print("$info\n");
		$info=~s,.,-,g;
		print("$info\n");
		foreach $areq (reverse(sort( { ${ $AERRORS{$code} }{$a} <=> ${ $AERRORS{$code} }{$b} } keys( %{ $AERRORS{$code} } )))) {
			#$info="areq=$areq";
			#print("$info\n");
			#$info="code/areq: $code / $areq";
			#print("$info\n");
			#$info=~s,.,-,g;
			#print("$info\n");
			print(sprintf("% 7d", ${ $AERRORS{$code} }{$areq})." $code $areq\n");
		}
		print("\n");
	}
	if ($newline==1) { print("\n\n\n"); }
	
	$newline=0;
	foreach $code (sort(keys(%VERRORS))) {
		$newline=1;
		$info="HTTP $code";
		print("$info\n");
		$info=~s,.,-,g;
		print("$info\n");
		foreach $vreq (reverse(sort( { ${ $VERRORS{$code} }{$a} <=> ${ $VERRORS{$code} }{$b} } keys( %{ $VERRORS{$code} } )))) {
			#$info="vreq=$vreq";
			#print("$info\n");
			#$info="code/vreq: $code / $vreq";
			#print("$info\n");
			#$info=~s,.,-,g;
			#print("$info\n");
			print(sprintf("% 7d", ${ $VERRORS{$code} }{$vreq})." $code $vreq\n");
		}
		print("\n");
	}
	if ($newline==1) { print("\n\n\n"); }
}



##########################################################################
# sub hour_stat_short()
# global:  %HHLIST
# @param   void
# @return  void
sub hour_stat_short {
	#my ()=@_;
	# @_ = ARGS call function
	my ($ii, $hh);
	
	print("Per-Hour Traffic Summary (Short)\n");
	print("================================\n");
	print("time:        |     GET |    HEAD |    POST | OPTIONS |    PUT |   DELETE |\n");
	print("-------------+---------+---------+---------+---------+--------+----------+\n");
	foreach $ii (sort(keys(%HHLIST))) {
		$hh=sprintf("%02d",$ii);
		print(sprintf("%02d:00-%02d:00  | % 7d | % 7d | % 7d | % 7d | % 7d | % 7d |", $hh, $hh+1, $HHLIST{$hh}{'GET'}, $HHLIST{$hh}{'HEAD'}, $HHLIST{$hh}{'POST'}, $HHLIST{$hh}{'OPTIONS'}, $HHLIST{$hh}{'PUT'}, $HHLIST{$hh}{'DELETE'})."\n");
	}
}



##########################################################################
# sub hour_stat_long()
# global:  %HHLIST
# @param   void
# @return  void
sub hour_stat_long {
	#my ()=@_;
	# @_ = ARGS call function
	my ($ii, $hh);
	
	print("Per-Hour Traffic Summary (Long)\n");
	print("===============================\n");
	print("time:        |     GET |    HEAD |    POST | OPTIONS |    PUT |   DELETE |    TRACE |    TRACK |  CONNECT |\n");
	print("-------------+---------+---------+---------+---------+--------+----------+----------+----------+----------+\n");
	foreach $ii (sort(keys(%HHLIST))) {
		$hh=sprintf("%02d",$ii);
		print(sprintf("%02d:00-%02d:00  | % 7d | % 7d | % 7d | % 7d | % 7d | % 7d | % 7d | % 7d | % 7d |", $hh, $hh+1, $HHLIST{$hh}{'GET'}, $HHLIST{$hh}{'HEAD'}, $HHLIST{$hh}{'POST'}, $HHLIST{$hh}{'OPTIONS'}, $HHLIST{$hh}{'PUT'}, $HHLIST{$hh}{'DELETE'}, $HHLIST{$hh}{'TRACE'}, $HHLIST{$hh}{'TRACK'}, $HHLIST{$hh}{'CONNECT'})."\n");
	}
}



##########################################################################
# sub top_stat()
# global:  %MYARG %TYPES %NAMES %IPADDR
# @param   void
# @return  void
sub top_stat {
	#my ()=@_;
	# @_ = ARGS call function
	my ($info, $ii);
	my ($ipaddr, $meth, $areq, $vreq, $vers, $code);
	
	$info="top $MYARG{'maxlines'} requests ipaddr";
	print("$info\n");
	print(" cnt      | request ipaddr\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $ipaddr (reverse sort { $IPADDR{$a} <=> $IPADDR{$b} } keys(%IPADDR)) {
		print(sprintf("% 9d",$IPADDR{$ipaddr}) ." | $ipaddr\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
	
	$info="top $MYARG{'maxlines'} requests methods";
	print("$info\n");
	print(" cnt      | request method\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $meth (reverse sort { $METH{$a} <=> $METH{$b} } keys(%METH)) {
		print(sprintf("% 9d",$METH{$meth}) ." | $meth\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
	
	$info="top $MYARG{'maxlines'} requests paths";
	print("$info\n");
	print(" cnt      | request path\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $areq (reverse sort { $AREQ{$a} <=> $AREQ{$b} } keys(%AREQ)) {
		print(sprintf("% 9d",$AREQ{$areq}) ." | $areq\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
	
	$info="top $MYARG{'maxlines'} requests vhosts paths";
	print("$info\n");
	print(" cnt      | request vhost/path\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $vreq (reverse sort { $VREQ{$a} <=> $VREQ{$b} } keys(%VREQ)) {
		print(sprintf("% 9d",$VREQ{$vreq}) ." | $vreq\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
	
	$info="top $MYARG{'maxlines'} requests versions";
	print("$info\n");
	print(" cnt      | request version\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $vers (reverse sort { $VERS{$a} <=> $VERS{$b} } keys(%VERS)) {
		print(sprintf("% 9d",$VERS{$vers}) ." | $vers\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
	
	$info="top $MYARG{'maxlines'} response codes";
	print("$info\n");
	print(" cnt      | response code\n");
	$info=~s,.,-,g;
	print("$info\n");
	$ii=0;
	foreach $code (reverse sort { $CODE{$a} <=> $CODE{$b} } keys(%CODE)) {
		print(sprintf("% 9d",$CODE{$code}) ." | $code\n");
		if ($ii++>=$MYARG{'maxlines'}) { last; }
	}
	print("\n");
	print("\n");
	print("\n");
}



##########################################################################
# sub hash_key2_sum(\%HASH, $kname)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @param   string $kname
# @return  int    $sumlist
sub hash_key2_sum {
	my ($RHASH, $kname)=@_;
	# @_ = ARGS call function
	my ($sumlist, $key, $ksec);
	
	$sumlist=0;
	foreach $key (sort(keys(%{$RHASH}))) {
		$sumlist=$sumlist+ ${$RHASH}{$key}{$kname};
	}
	return $sumlist;
}



##########################################################################
# sub hash_count(\%HASH)
# global:  -----
# @param   HASH   \%HASH - reference to HASH
# @return  int
sub hash_count {
	my ($RHASH)=@_;
	# @_ = ARGS call function
	my (@KLIST);
	
	@KLIST=keys(%{$RHASH});
	return $#KLIST+1;
}



sub usage {
#print("|@_|\n");
printf qq{Usage: $self [-d <today|yesterday>] [-h <cnt>] [-u <cnt>] [file1]
       $self --[version|help]
};
}


