#!/usr/bin/perl -w use strict; use warnings; use Time::HiRes 'time'; use List::Util qw/sum/; use Storable; #use Data::Dumper; my %data; sub init { my $data = $_[0]; $data->{count} = 0; $data->{counts} = [0]; $data->{one} = 0; $data->{five} = 0; $data->{fifteen} = 0; init_time($data); } sub init_time { # mode 0 == init all # mode 1 == init time and second only # mode 2 == init minute only my $data = $_[0]; my $mode = $_[2] || 0; unless ($mode == 2) { $data->{time} = $_[1] || time(); } return if $mode == 1; $data->{minute} = $data->{time} + 60; } while (<>) { my $nt = time(); my ($vhost, $method, $url, $code, $bytes, $referrer, $ua) = (m/ ^(\S+)\s # vhost \S+\s # IP \S+\s \S+\s \[[^]]+\]\s # date "(\S+)\s # method ((?:[^"]*(?:\\")?)*)\s # URL [^"]*"\s # protocol (\S+)\s # status code (\S+)\s # bytes "((?:[^"]*(?:\\")?)*)"\s # referrer "(.*)"$ # user agent /x); die "Couldn't match $_" unless $vhost; # vhost counts $data{vhosts}{$vhost} ||= {}; if (tally($data{vhosts}{$vhost}, $nt)) { show($data{vhosts}{$vhost}, " $vhost\n"); } # TODO: urls? user agents? referrers? status codes? # total hit count if (tally(\%data, $nt)) { print "\n"; show(\%data, " total *\n\n"); store(\%data, "logstats.data.tmp"); rename("logstats.data.tmp", "logstats.data"); } } sub tally { my $data = $_[0]; # reset $count every second init($data) unless defined $data->{count}; $data->{count}++; my $nt = $_[1] || time(); my $diff = $nt - $data->{time}; my $gimme_a_sec = 0; if ($diff >= 1) { $gimme_a_sec = 1; init_time($data, $nt, 1); $data->{hps} = $data->{count} / $diff; $data->{count} = 0; # keep per-minute count $data->{counts}[0] += $data->{hps}; # update per-minute counter $diff = $nt - $data->{minute}; if ($diff >= 0) { init_time($data, $nt, 2); # log "0" counts if this is an infrequent stat my $count = $data->{counts}[0]; $data->{counts}[0] = 0; while ($diff >= 60) { unshift @{$data->{counts}}, 0; $diff -= 60; } $data->{counts}[0] = $count; unshift @{$data->{counts}}, 0; no warnings qw/uninitialized misc/; splice @{$data->{counts}}, 16; my @count = @{$data->{counts}}; $data->{one} = $count[1] / 60; $data->{five} = sum(@count[1..5]) / 5 / 60; $data->{fifteen} = sum(@count[1..15]) / 15 / 60; } else { # extrapolate running average $diff += 60; my $count = $data->{counts}[0]; $count *= 60 / $diff; my @count = @{$data->{counts}}; defined($count[1]) or $count[1] = $count; $data->{one} = sum($count, $count[1]) / 2 / 60; no warnings 'uninitialized'; $data->{five} = sum($count, @count[1..5]) / 6 / 60; $data->{fifteen} = sum($count, @count[1..15]) / 16 / 60; } } return $gimme_a_sec; } sub show { my $data = $_[0]; print scalar localtime($data->{time}); printf " hps: %6.0f, average: %.2f, %.2f, %.2f, ", $data->{hps}, $data->{one}, $data->{five}, $data->{fifteen}; print $_[1] || "\n"; }