#!/usr/bin/perl

=head1 NAME

benchtimes -- utility to analyze benchtimes logging for Interchange

=head1 SYNOPSIS

  benchtimes [-n count] [-t timespec] file

=head1 DESCRIPTION

This small tool analyzes the CPU usage of Interchange by session id
and IP address. It shows the top users in seconds of time and number of hits.

=cut

use Getopt::Std;
use strict;
my %opt;
getopts('n:t:', \%opt);

use POSIX qw/strftime/;
my %session_time;
my %session_hits;
my %total_time;
my %total_hits;
my %earliest;
my %bigpages;
my %latest;
my %info;

# Parses a time specification such as "1 day" and returns the
# number of seconds in the interval, or undef if the string could
# not be parsed.
sub time_to_seconds {
	my($str) = @_;
	my($n, $dur);

	($n, $dur) = ($str =~ m/(\d+)[\s\0]*(\w+)?/);
	return undef unless defined $n;
	if (defined $dur) {
		local($_) = $dur;
		if (m/^s|sec|secs|second|seconds$/i) {
		}
		elsif (m/^m|min|mins|minute|minutes$/i) {
			$n *= 60;
		}
		elsif (m/^h|hour|hours$/i) {
			$n *= 60 * 60;
		}
		elsif (m/^d|day|days$/i) {
			$n *= 24 * 60 * 60;
		}
		elsif (m/^w|week|weeks$/i) {
			$n *= 7 * 24 * 60 * 60;
		}
		else {
			return undef;
		}
	}

	$n;
}

=head1 OPTIONS

=over 4

=item -n count

The number of entries to display for each type. This
would display 16 of each:

    benchtimes -n 16 /var/lib/interchange/foundation/logs/bench.log

Default is 10.

=item -t timespec

Determines how far back in time you want to go. Accepts a standard Interchange
time specification, i.e. "1 day" or "3 hours". This would summarize for
the past day:

	benchtimes -t 1d /var/lib/interchange/foundation/logs/bench.log

Default is not set, meaning all log lines are analyzed.

=cut

my %start;

my $first = 0;
my $now = time();
if($opt{t}) {
	my $minus = time_to_seconds($opt{t});
	$first = $now - $minus;
}

while(<>) {
	chomp;
	my ($ip, $path, $sessionid, $pid, $time, $id, $bench) = split /\t/, $_;
	next unless $time > $first;
	if($bench) {
		$session_hits{$sessionid}++;
		$session_time{$sessionid} += $bench;
		$total_hits{$ip}++;
		$total_time{$ip} += $bench;
		$bigpages{$id} = $bench;
		$info{$id} = "ip=$ip path=$path pid=$pid time=$bench";
		unless($start{$id}) {
			warn "Couldn't find start for $_\n";
		}
		elsif(! $time) {
			warn "No end time for $_\n";
		}

		$latest{$ip} = $time;
		$latest{$sessionid} = $time;
	}
	else {
		$start{$id} = $time;
		$earliest{$ip} ||= $time;
		$earliest{$sessionid} ||= $time;
	}
}

my %display = (
	session_time  => \%session_time,
	session_hits  => \%session_hits,
	total_hits    => \%total_hits,
	total_time    => \%total_time,
);

my %fmt = (
	session_time  => '%9.3f seconds',
	session_hits  => '%6d hits',
	total_hits    => '%6d hits',
	total_time    => '%9.3f seconds',
	bigpages      => '%9.3f seconds',
);

my $started;
my $n = $opt{n} || 10;

no strict 'refs';
for my $tag (sort keys %display) {
	my $ref = $display{$tag}
		or warn "No reference found for $tag. Skip.\n", next;
	print "\n" if $started++;
	my @keys = sort { $ref->{$b} <=> $ref->{$a} } keys %$ref;
	print "Displaying top $n by $tag\n";
	print "---------------------------------\n";
	for(my $i = 0; $i < $n; $i++) {
		my $id = $keys[$i] or next;
		my $early = strftime('%m-%d %H:%M:%S', localtime($earliest{$id}));
		my $late  = strftime('%m-%d %H:%M:%S', localtime($latest{$id}));
		printf "%-20s $fmt{$tag} (from %s to %s)\n", $id, $ref->{$id}, $early, $late;
	}
}

BIGP: {
	my $tag = 'bigpages';
	my $ref = \%bigpages
		or warn "No reference found for bigpages. Skip.\n", next;
	print "\n" if $started++;
	my @keys = sort { $ref->{$b} <=> $ref->{$a} } keys %$ref;
	print "Displaying top $n by $tag\n";
	print "---------------------------------\n";
	for(my $i = 0; $i < $n; $i++) {
		my $id = $keys[$i] or next;
		printf "%-20s $fmt{$tag}\n(%s)\n", $id, $ref->{$id}, $info{$id};
	}
}
=head1 SEE ALSO

extensions/benchtimes

=head1 AUTHOR

Mike Heins, <mike (at domain) perusion.com>.

=head1 BUGS

None known at this time.

=cut
