|
Tutto è pronto per il nocciolo
del nostro lavoro, che grazie alla preliminare costruzione delle
espressioni regolari si rivela particolarmente semplice.
# ritorna un array con i parametri di input per Main
# che possono essere o la linea di comando o la QUERY_STRING
# imposta la variabile globale IsCgi a 1
# se si tratta di una cgi, 0 altrimenti
sub GetParam {
$IsCgi = 0;
return @ARGV if(@ARGV) ;
$IsCgi = exists $ENV{'QUERY_STRING'};
if($IsCgi) {
$_=$ENV{'QUERY_STRING'};
foreach(split(/&/)) {
s/\+/ /g;
s/%([0-9a-fA-F][0-9a-fA-F])/sprintf("%c",hex($1))/eg;
unshift(@ARGV, $_);
}
}
return @ARGV;
}
# analisi dei parametri in input
# input: @_
# output: $LogFile, $Begin, $End, $Url, $NUrl, $Site , $NSite
sub ParseParam {
local $t, $u;
local %msg=
('url=', 'Includi URL', 'url=!', 'Escludi URL',
'site=', 'Includi Sito', 'site=!', 'Escludi sito');
$param_limit = '';
while($_=shift) {
$LogFile=$1 if !$IsCgi && /^file=(.+)/;
($Begin=date2int($param_begin=$1)
|| &PrintexError("Data di inizio errata"))
if(/^begin=(.+)/);
($End=date2int($param_end=$1)
|| &PrintexError("Data di fine errata"))
if(/^end=(.+)/);
if(/^(url=!?|site=!?)(.+)/) {
$param_limit .= "$msg{$1}: $2<br>\n";
# converti i parametri in espressione regolare
$t = $1; $_=$2;
s/\W/\\$1/g; s/\\\?/\./g;
s/\\\*/\.\*/g;
$u=$_; $_=$t;
$Url .= "$u|" if /^url=$/;
$NUrl .= "$u|" if /^url=!$/;
$Site .= "$u|" if /^site=$/;
$NSite .= "$u|" if /^nsite=!$/;
}
}
$Url =~ s/\|$//;
$NUrl =~ s/\|$//;
$Site =~ s/\|$//;
$NSite =~ s/\|$//;
}
# abbreviazione dei mesi dell'anno, in italiano e in inglese
%MONTHS =
('jan', 1, 'feb', 2, 'mar', 3, 'apr', 4, 'may', 5, 'jun', 6,
'jul', 7, 'aug', 8, 'sep', 9, 'oct', 10, 'nov', 11, 'dec', 12,
'gen', 1, 'feb', 2, 'mar', 3, 'apr', 4, 'mag', 5, 'giu', 6,
'lug', 7, 'ago', 8, 'set', 9, 'ott', 10, 'nov', 11, 'dic', 12);
# date2int <date>
# converte una data come 9/Lug/1968 in un numero come 19680709
# (facile da comparare); ritorna 0 se la data e' errata
sub date2int {
$_=shift; tr/A-Z/a-z/;
return $3 * 10000 + $MONTHS{$2}*100 + $1
if /^(\d\d?)\/(\w\w\w)\/(\d\d\d\d)$/ && $MONTHS{$2};
return 0;
}
Si apre il file e si legge riga per riga, separando i campi con una
split e selezionando quelli che ci interessano con un cosiddetto
array slice:
($site, $_, $url, $status, $tot) = (split) [0,3,6,8,9]
In Perl è possibile utilizzare
le parentesi quadre non solo per indicizzare un array ma anche per
selezionare un sottoinsieme dell’array (appunto uno slice).
Nell’analisi dell’input ci
sono due finezze: innanzitutto occorre considerare soltanto le
richieste che non hanno dato errore ($status == 200):
tipicamente i log sono pieni di richieste andate male (richieste di
URL sbagliati per esempio), che sebbene relativamente numerose sono
del tutto irrilevanti dal punto di vista statistico e “sporcano”
il risultato con una miriade di casi strani. Un altro aggiustamento
da fare è togliere dalle chiamate di CGI la parte che
specifica i parametri della CGI (che altrimenti apparirebbero
tutte come URL distinte mentre in realtà si riferiscono ad un
insieme ristretto di URL). Le URL che invocano una CGI hanno
un formato simile a questo:
/cgi-bin/try?a=1&b=2
Per “pulire” le URL
togliamo tutto quanto segue un ? alla fine della stringa. Il
resto del programma semplicemente confronta le date ed esegue il
matching per i vari casi, scartando le righe che non soddisfano le
condizioni. Quando una riga soddisfa tutte le restrizioni, grazie ad
un array associativo si conteggiano contatti e byte per ogni URL e
per ogni sito:
++$StatUrlCount{$url};
$StatUrlTot{$url} += $tot;
++$StatReqCount{$site};
$StatReqTot{$site} += $tot;
Nella procedura di stampa dei risultati
vengono semplicemente analizzati e stampati i risultati contenuti
negli array associativi risultanti.
# stampa i risultati ed esci
sub PrintexResults {
PrintBegin("AnaLog $VERSION");
print "<h2>¦ 1996 Michele Sciabarrà</h2>";
print "<h2>Inizio: $param_begin</h2>\n" if($param_begin) ;
print "<h2>Fine: $param_end</h2>\n" if($param_end) ;
print "<h2>\n$param_limit</h2>\n";
print "<h1>Statistica Richieste</h1>\n<h2></h2>";
print "<table>\n";
print "<tr><th>URL <th>Conteggio <th>Totale byte <br>\n";
foreach(sort { -( $StatUrlCount{$a} <=> $StatUrlCount{$b} )
|| $a cmp $b }
keys %StatUrlCount) {
print "<tr><td>$_ <td>$StatUrlCount{$_} ".
"<td><font size=-1>$StatUrlTot{$_}</font><br>\n";
}
print "</table>\n";
print "<h1>Statistica Contatti</h1>\n";
print "<table>\n";
print "<tr><th>Sito <th>Conteggio <th>Totale byte <br>\n";
foreach(sort { - ($StatReqCount{$a} <=> $StatReqCount{$b})
|| $a cmp $b } keys %StatReqCount) {
print "<tr><td>$_ <td>$StatReqCount{$_} ".
"<td><font size=-1>$StatReqTot{$_} </font><br>\n";
}
print "</table>\n";
PrintexEnd;
}
La procedura sarebbe banale se non fosse per l’uso “avanzato”
della primitiva sort utilizzato per stampare il risultato in
maniera ordinata:
foreach(sort { -( $StatUrlCount{$a} <=> $StatUrlCount{$b} )
|| $a cmp $b } keys %StatUrlCount) {
print "<tr><td>$_ <td>$StatUrlCount{$_} ".
"<td><font size=-1>$StatUrlTot{$_}</font><br>\n";
}
La keys %StatUrlCount
fornisce tutti gli indici dell’array associativo (le “chiavi”).
Sfortunatamente l’ordine è pressoché casuale e
occorre bisogna ordinarlo per presentare il risultato in un formato
facilmente leggibile. Ma in base a quali criteri va fatto
l’ordinamento? Il criterio scelto è il seguente:
vogliamo avere per prime le URL che hanno contato il maggior numero
di contatti, ma a pari numero di contatti vogliamo il risultato
ordinato alfabeticamente.
Il Perl ha una primitiva di sort
applicabile ad un array che consente di specificare con un blocco di
codice la funzione di comparazione di due elementi. Tipicamente tutti
gli algoritmi di sort possono essere parametrizzati in base al metodo
di accesso agli elementi (il cosiddetto selettore) e a una funzione
di comparazione tra elementi. In Perl poiché gli elementi sono
contenuti in un array non abbiamo problemi di specificare il
selettore: si indicizza l’array. Per specificare funzione di
comparazione bisogna fornire un blocco che compari due elementi e
ritorni -1, 0 o 1 a seconda che il primo elemento sia minore, uguale
o maggiore del secondo.
Il blocco viene valutato per comparare
ogni coppia di elementi che l’algoritmo necessita di
confrontare per ordinare l’array. Per ragioni di efficienza non
si tratta di una subroutine e non viene fatto un passaggio di
parametri: gli elementi da comparare vengono posti nelle variabili $a
e $b. Il default è il blocco { $a cmp $b }, che
compara due stringhe. Noi abbiamo adottato:
{ - ($StatReqCount{$a} <=> $StatReqCount{$b}) || $a cmp $b }
In questo modo si ordina innanzitutto
per numero di accessi come sono stati conteggiati nello $StatReqCount
e in caso di eguaglianza di numero di accessi ( se <=>
ritorna 0, che è falso) si effettua la comparazione alfabetica
tra le chiavi. Notare il meno, che serve ad invertire il risultato
del <=> e ordinare in senso decrescente il numero di accessi.
|