4 # Copyright (c) 2003 Henk Penning, all rights reserved.
5 # penning@cs.uu.nl, http://www.cs.uu.nl/staff/henkp.html
6 # Version 1.1 was donated to the Apache Software Foundation 2003 Jan 28
7 # $Id: mirmon,v 1.37 2006/12/04 15:16:11 henkp Exp henkp $
8 # Permission is hereby granted, free of charge, to any person obtaining a
9 # copy of this software and associated documentation files (the "Software"),
10 # to deal in the Software without restriction, including without limitation
11 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 # and/or sell copies of the Software, and to permit persons to whom the
13 # Software is furnished to do so, subject to the following conditions:
15 # The above copyright notice and this permission notice shall be included in
16 # all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 # DEALINGS IN THE SOFTWARE.
26 # Thanks to Klaus Heinz <heinz@NetBSD.org> for sugestions ao htm_head
29 my $VER = '$Id: mirmon,v 1.37 2006/12/04 15:16:11 henkp Exp henkp $' ;
36 my $DEF_CNF = "/etc/$PRG.conf" ;
51 qw( web_page state countries mirror_list probe
52 project_name project_url icons
55 qw( project_logo min_poll min_sync max_sync list_style htm_top htm_foot
58 my %CNF_KEYS ; for ( @REQ_KEYS, @OPT_KEYS, keys %CNF )
59 { $CNF_KEYS { $_ } ++ ; }
61 my $TIM_PAT = '^(\d+)([smhd])$' ;
62 my @LIST_STYLE = qw(plain apache) ;
63 my @GET_OPTS = qw(all update) ;
64 my @PUT_HGRAM = qw(top bottom nowhere) ;
66 my %APA_TYPES = () ; for ( qw(backup ftp http) ) { $APA_TYPES { $_ } ++ ; }
68 my $prog = substr($0,rindex($0,'/')+1) ;
70 Usage: $prog [ -v ] [ -q ] [ -t timeout ] [ -get opt ] [ -c conf ]
73 option t : set timeout [ default $CNF{timeout} ] ;
74 option get : 'all' : probe all sites
75 : 'update' : probe a selection of the sites (see doc)
76 option c : configuration file [ default $DEF_CNF ]
77 -------------------------------------------------------------------
78 Documentation : the program contains 'pod' style documentation.
79 Extract the doc with 'pod2text $prog' or 'pod2html $prog OUT', etc.
80 -------------------------------------------------------------------
82 sub Usage { die "$_[0]$Usage" ; }
83 sub Error { die "$prog: $_[0]\n" ; }
84 sub Warn { warn "$prog: $_[0]\n" ; }
86 # usage: &GetOptions(ARG,ARG,..) defines $opt_ID as 1 or user spec'ed value
87 # usage: &GetOptions(\%opt,ARG,ARG,..) defines $opt{ID} as 1 or user value
88 # ARG = 'ID' | 'ID=SPC' | 'ID:SPC' for no-arg, required-arg or optional-arg
89 # ID = perl identifier
90 # SPC = i|f|s for integer, fixedpoint real or string argument
93 Getopt::Long::config('no_ignore_case') ;
94 # Usage() unless GetOptions() ;
95 my %opt = () ; Usage() unless GetOptions (\%opt,'v','q','t=i','get=s','c=s') ;
96 Usage("Arg count\n") unless @ARGV >= 0 ;
99 my $GET = IO::Select -> new () ;
107 # <META HTTP-EQUIV=Expires CONTENT="Tue, 04 Dec 1993 21:29:02 GMT">
109 { my @day = qw(Sun Mon Tue Wed Thu Fri Sat) ;
110 my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) ;
111 my @gmt = gmtime time + 3600 ;
112 sprintf "%s, %02d %s %4d %02d:%02d:%02d GMT"
113 , $day [ $gmt [ 6 ] ]
115 , $mon [ $gmt [ 4 ] ]
122 { return $opt{c} if $opt{c} ;
123 my $HOME = ( getpwuid $< ) [ 7 ] or Error "can get homedir '$<' ($!)" ;
124 my @LIST = ( "$PRG.conf" , "$HOME/.$PRG.conf" , $DEF_CNF ) ;
125 for my $conf ( @LIST ) { return $conf if -f $conf ; }
126 Error sprintf "can't find a config file :\n %s" , join "\n ", @LIST ;
130 { print "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" ;
131 for my $key ( sort keys %CNF )
132 { next if $key =~ m/^_/ ;
133 print "show_conf : $key = '$CNF{$key}'\n" ;
135 for my $key ( sort keys %HREF )
136 { printf "show_conf : for site '%s' use instead\n '%s'\n",
137 $key, $HREF { $key } if $opt{v} ;
139 printf "show_conf : included '%s'\n", join "', '", @{ $CNF{_include} } ;
140 print "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" ;
148 if ( grep $_ eq $FILE, @{ $CNF {_include} } )
149 { Error "already included : '$FILE'" ; }
151 { push @{ $CNF {_include} }, $FILE ; }
153 open FILE, $FILE or Error "can't open '$FILE' ($!)" ;
154 my $CONF = join "\n", grep /./, <FILE> ;
157 $CONF =~ s/\t/ /g ; # replace tabs
158 $CONF =~ s/^[+ ]+// ; # delete leading space, plus
159 $CONF =~ s/\n\n\s+/ /g ; # glue continuation lines
160 $CONF =~ s/\n\n\+\s+//g ; # glue concatenation lines
161 $CONF =~ s/\n\n\./\n/g ; # glue concatenation lines
164 print "--$CONF--\n" if $opt{d} ;
165 for ( grep ! /^#/, split /\n\n/, $CONF )
166 { my ($key,$val) = split ' ', $_, 2 ;
167 $val = '' unless defined $val ;
168 print "conf '$FILE' : key '$key', val '$val'\n" if $opt{d} ;
169 if ( exists $CNF_KEYS { $key } )
170 { $CNF { $key } = $val ; }
171 elsif ( $key eq 'site_url' )
172 { my ( $site, $url ) = split ' ' , $val ;
173 $url .= '/' unless $url =~ m!/$! ;
174 $HREF { lc $site } = $url ;
175 printf "config : for site '%s' use instead\n '%s'\n",
176 $site, $url if $opt{v} ;
178 elsif ( $key eq 'env' )
179 { my ( $x, $y ) = split ' ' , $val ;
181 printf "config : setenv '%s'\n '%s'\n", $x, $y if $opt{v} ;
183 elsif ( $key eq 'no_randomize' )
184 { $CNF { randomize } = 0 ; }
185 elsif ( $key eq 'include' )
187 elsif ( $key eq 'show' )
188 { show_conf unless $opt{q} ; }
189 elsif ( $key eq 'exit' )
190 { Error 'exit per config directive' ; }
191 elsif ( $key eq 'max_age' )
192 { $CNF { max_sync } = $val ; }
195 Error "unknown keyword '$key' (value '$val')" ;
203 $CNF { timeout } = $opt{t} if $opt{t} ;
204 for my $key ( @REQ_KEYS )
205 { unless ( exists $CNF { $key } )
206 { $err .= "$prog error: missing config for '$key'\n" ; }
208 for my $key ( qw(min_poll max_poll max_sync min_sync) )
209 { my $max = $CNF { $key } ;
210 unless ( $max =~ /$TIM_PAT/o )
211 { $err .= "$prog error: $key ($max) doesn't match /$TIM_PAT/\n" ; }
213 unless ( grep $CNF { list_style } eq $_, @LIST_STYLE )
214 { $err .= sprintf "%s : error: unknown 'list_style' '%s'\n",
215 $prog, $CNF { list_style } ;
217 unless ( grep $CNF { put_histo } eq $_, @PUT_HGRAM )
218 { $err .= sprintf "%s : error: unknown 'put_histo' '%s'\n",
219 $prog, $CNF { put_histo } ;
221 if ( $opt { get } and not grep $opt { get } eq $_, @GET_OPTS )
222 { $err .= sprintf "%s : error: unknown 'get option' '%s'\n",
223 $prog, $opt { get } ;
226 $opt{q} = 0 if $opt{v} ;
231 my %tab = ( 's' => 1, 'm' => 60, 'h' => 60 * 60, 'd' => 60 * 60 * 24 ) ;
232 Error "wrong time '$tim'" unless $tim =~ /$TIM_PAT/o ;
233 my $m = $1 ; my $u = $2 ;
234 return $m * $tab { $u } ;
237 sub aprx_eq { my ( $t1, $t2 ) = @_ ; abs ( $t1 - $t2 ) < 60 ; }
238 sub aprx_ge { my ( $t1, $t2 ) = @_ ; $t1 > $t2 or aprx_eq $t1, $t2 ; }
239 sub aprx_le { my ( $t1, $t2 ) = @_ ; $t1 < $t2 or aprx_eq $t1, $t2 ; }
240 sub aprx_gt { my ( $t1, $t2 ) = @_ ; $t1 > $t2 and not aprx_eq $t1, $t2 ; }
241 sub aprx_lt { my ( $t1, $t2 ) = @_ ; $t1 < $t2 and not aprx_eq $t1, $t2 ; }
245 my ( $magn, $unit ) ;
246 my $mins = $s / 60 ; my $m = int ( $mins + 0.5 ) ;
247 my $hours = $s / ( 60 * 60 ) ; my $h = int ( $hours + 0.5 ) ;
250 { $magn = $s ; $unit = 'second' ; }
252 { $magn = $m ; $unit = 'minute' ; }
254 { $magn = $h ; $unit = 'hour' ; }
256 { $magn = sprintf "%.1f", $hours / 24 ; $unit = 'day' ; }
258 $unit .= 's' unless $magn == 1 ;
260 return "$magn $unit" ;
264 { ( tim_to_s $CNF { min_sync } ) + ( tim_to_s $CNF { max_poll } ) ; }
266 { ( tim_to_s $CNF { max_sync } ) + ( tim_to_s $CNF { max_poll } ) ; }
269 { ( tim_to_s $CNF { min_poll } ) + ( tim_to_s $CNF { max_poll } ) ; }
273 return 'z' unless $time =~ /^\d+$/ ;
275 ( ( aprx_ge ( $time, $^T - max_age1 ) )
277 : ( aprx_ge ( $time, $^T - max_age2 ) ? 'b' : 'f' )
284 printf "*** %-10s %s\n", $stat, $url unless $opt{q} ;
285 my ( $time, $vrfy, $hstp, $hsts ) ;
286 if ( exists $OLD { $url } )
287 { $time = $OLD { $url } [ 0 ] ;
288 $vrfy = $OLD { $url } [ 2 ] ;
289 $hstp = substr $OLD { $url } [ 3 ], 1 - $HIST ;
290 $hsts = $OLD { $url } [ 4 ] ;
298 $RES { $url } = [ $time, $stat, $vrfy, $hstp . 'f', $hsts, $^T ] ;
306 ( exists $OLD { $url }
307 ? substr ( $OLD { $url } [ 3 ], 1 - $HIST )
310 my $hsts = ( exists $OLD { $url } ? $OLD { $url } [ 4 ] : '') ;
311 printf "result %d %s\n", $time, $url if $opt{v} ;
312 $RES { $url } = [ $time, $stat, $^T, $hstp . 's', $hsts, $^T ] ;
317 open STT, $STT or Error "can't open '$STT' ($!)" ;
320 my ( $url, $time, $stat, $vrfy, $hstp, $hsts, $lprb ) = split ' ' ;
322 $hstp = '' unless defined $hstp ;
323 $hsts = '' unless defined $hsts ;
324 $hsts = '' if $hsts eq 'undef' ;
325 $lprb = 'undef' unless defined $lprb ;
326 $OLD { $url } = [ $time, $stat, $vrfy, $hstp, $hsts, $lprb ] ;
334 printf "check_hist: last '$time' hsts '$hsts'\n" if $opt{d} ;
337 my ( $stmp, $hist ) ;
340 { $stmp = 0 ; $hist = '' ; }
342 { ( $stmp, $hist ) = split '-', $hsts ; }
344 if ( aprx_le $stmp, $^T - tim_to_s '1d' )
345 { $res = sprintf "%s-%s%s"
347 , substr ( $hist, 1 - $HIST )
356 my $TMP = "$STT.tmp" ;
357 open TMP, ">$TMP" or Error "can't write '$TMP' ($!)" ;
358 for my $url ( sort keys %RES )
359 { $RES { $url } [ 4 ]
360 = check_hist $RES { $url } [ 0 ], $RES { $url } [ 4 ] ;
361 my @OUT = @{ $RES { $url } } ;
362 $OUT [ 1 ] =~ s/\s/_/g ;
363 printf TMP "%s %s\n", $url, join ' ', @OUT
364 or Error "can't print to $TMP ($!)" ;
368 { Warn "wrote empty state file; keeping previous version" ; }
370 { rename $TMP, $STT or Error "can't rename '$TMP', '$STT' ($!)" ; }
375 open CCS, $CCS or Error "can't open '$CCS' ($!)" ;
379 my ( $code, $dash, $reg ) = split ' ', $_, 3 ;
380 $CCS { lc $code } = lc $reg ;
387 my ( $type, $site, $home ) ;
388 if ( $url =~ m!^(ftp|http)://([^/:]+)(:\d+)?/! )
389 { $type = $1 ; $site = $2 ; $home = $& ; }
390 return $type, $site, $home ;
393 sub type { my ( $t, $s, $h) = type_site $_[0] ; $t ; }
394 sub site { my ( $t, $s, $h) = type_site $_[0] ; $s ; }
395 sub home { my ( $t, $s, $h) = type_site $_[0] ; $h ; }
400 open LST, $LST or Error "can't open '$LST' ($!)" ;
405 if ( $CNF { list_style } eq 'plain' )
406 { ( $reg, $url ) = split ' ' ;
407 unless ( $url =~ m!/$! )
408 { print "*** mirmon appended '/' to $url\n" unless $opt{q} ;
412 elsif ( $CNF { list_style } eq 'apache' )
414 ( $apache_type, $reg, $url ) = split ' ' ;
415 unless ( defined $APA_TYPES { $apache_type } )
416 { print "*** strange type : $apache_type\n" unless $opt{q} ;
419 unless ( $url =~ m!/$! )
420 { print "*** missing '/' in $url\n" unless $opt{q} ;
425 my $site = site $url ;
426 my $type = type $url ;
428 unless ( defined $site )
429 { print "*** strange url : '$url'\n" unless $opt{q} ; next ; }
431 $LST { $url } = [ $type , $site, $reg ] ;
435 sub url { sprintf '<A HREF="%s">%s</A>', $_[0], $_[1] ; }
436 sub nam { sprintf '<A NAME="%s">%s</A>', $_[0], $_[1] ; }
437 sub SMA { sprintf "<FONT SIZE=\"-1\">%s</FONT>", $_[0] ; }
438 sub BLD { sprintf "<B>%s</B>", $_[0] ; }
439 sub NSS { sprintf SMA('%s site%s'), $_[0], ( $_[0] == 1 ? '' : 's' ) ; }
440 sub TAB { sprintf "<TABLE BORDER=2 CELLPADDING=3>%s</TABLE>", $_[0] ; }
441 sub TR { sprintf "<TR>%s</TR>\n", $_[0] ; }
442 sub TH { sprintf "<TH>%s</TH>\n", $_[0] ; }
443 sub TD { sprintf "<TD>%s</TD>\n", $_[0] ; }
444 sub TDr { sprintf "<TD ALIGN=\"RIGHT\">%s</TD>\n", $_[0] ; }
445 sub RED { sprintf "<FONT COLOR=\"RED\">%s</FONT>", $_[0] ; }
446 sub GRN { sprintf '<FONT COLOR="GREEN">%s</FONT>', $_[0] ; }
462 { $res = BLD 'renewed' ; }
464 { $res = pr_interval $^T - $time ;
465 $res = BLD RED $res if aprx_lt $time, $max ;
471 { sprintf '<IMG BORDER=1 SRC="%s/mm%s%02d.gif" ALT="">'
472 , $CNF { icons }, $_[0], $_[1] ;
475 sub img_sf { img_sf_cnt $_[0], 1 ; }
479 return '' unless $hst =~ m/^[sbfz]+$/ ;
480 if ( length $hst == $HIST and $hst =~ /^(s*b)s*$/ )
481 { return img_sf_cnt 'sb', length $1 ; }
482 elsif ( length $hst == $HIST and $hst =~ /^(s*f)s*$/ )
483 { return img_sf_cnt 'sf', length $1 ; }
484 elsif ( length $hst == $HIST and $hst =~ /^(s*b)fs*$/ )
485 { return img_sf_cnt 'sbf', length $1 ; }
488 my $prf = substr $hst, 0, 1 ;
489 $hst = substr $hst, 1 ;
491 { if ( substr ( $prf, 0, 1 ) eq substr ( $hst, 0, 1 ) )
493 $hst = substr $hst, 1 ;
496 { $res .= img_sf_cnt $prf, $cnt ;
497 $prf = substr $hst, 0, 1 ;
498 $hst = substr $hst, 1 ;
502 $res .= img_sf_cnt $prf, $cnt if $cnt ;
509 return '' if $hsts eq '' ;
510 my ( $t, $h ) = split '-', $hsts ;
511 if ( aprx_lt $t, $^T ) { $h .= age_code $time ; }
512 return show_hist substr $h, - $HIST ;
515 sub gen_histogram_probes
516 { my ( $time, $stat, $vrfy, $hstp, $hsts, $lprb ) ;
524 return '' unless scalar keys %RES ;
525 for my $url ( keys %RES )
526 { ( $time, $stat, $vrfy, $hstp, $hsts, $lprb ) = @{ $RES { $url } } ;
527 my $hr = int ( ( $^T - $lprb ) / 3600 + 0.5 ) ;
528 $hr_min = $hr if ! defined $hr_min or $hr < $hr_min ;
529 $hr_max = $hr if ! defined $hr_max or $hr > $hr_max ;
531 { $tab { $hr } ++ ; $s_cnt ++ ; }
533 { $bad { $hr } ++ ; $f_cnt ++ ; }
541 , $s_cnt , GRN ( 'successful' )
542 , $f_cnt , RED ( 'failed' )
547 for my $x ( keys %tab )
548 { my $tot = $tab { $x } + ( $bad { $x } || 0 ) ;
549 $max = $tot if $max < $tot ;
552 return "<BLOCKQUOTE>\nnothing yet\n</BLOCKQUOTE>\n" unless $max ;
554 for my $hr ( $hr_min .. $hr_max )
555 { my $x = $tab { $hr } || 0 ;
556 my $y = $bad { $hr } || 0 ;
557 my $n = int ( $x / $max * $HIST ) ;
558 my $b = int ( $y / $max * $HIST ) ;
564 ( ( $n ? img_sf_cnt ( 's', $n ) : '' )
565 . ( $b ? img_sf_cnt ( 'f', $b ) : '' )
566 . ( ( $n + $b ) ? '' : ' ' )
570 return "<BLOCKQUOTE>\n" . TAB ( $res ) . "</BLOCKQUOTE>\n" ;
574 { my $MAX_H = max_age1 ;
576 ( ( 20 * 3600 <= $MAX_H and $MAX_H <= 36 * 3600 )
577 ? int ( $MAX_H / 3600 )
580 my $MAX_O = max_age2 ;
581 my $MAX_o = int ( $MAX_O / 3600 + 0.5 ) ;
583 my %W = ( 'old' => 1, 'ded' => 1, 'bad' => 1 ) ;
584 my %Wmx = ( 'old' => 5, 'ded' => 3, 'bad' => 3 ) ;
588 for ( my $x = 0 ; $x < $MAX_h ; $x ++ ) { $tab { $x } = 0 ; }
589 $tab { old } = 0 ; $tab { ded } = 0 ; $tab { bad } = 0 ;
590 for my $url ( keys %RES )
591 { my $time = $RES { $url } [ 0 ] ;
592 if ( $time =~ /^\d+$/ )
593 { my $s = $^T - $time ;
594 my $hr = int ( $s / $MAX_H * ( $MAX_h - 1 ) + 0.5 ) ;
595 if ( $s <= $MAX_H ) { $tab { $hr } ++ ; }
596 elsif ( $s <= $MAX_O ) { $tab { old } ++ ; }
597 else { $tab { ded } ++ ; }
600 { $tab { bad } ++ ; }
603 for ( grep ! exists $Wmx { $_ }, keys %tab )
604 { $max = $tab { $_ } if $tab { $_ } > $max ; }
608 for my $aux ( keys %Wmx )
609 { $bad { $aux } = $tab { $aux } ;
610 if ( $bad { $aux } > $max )
611 { $W { $aux } = $Wmx { $aux } ;
612 my $d = int ( $bad { $aux } / $W { $aux } ) ;
613 for ( my $i = 1 ; $i < $W { $aux } ; $i++ )
614 { $tab { $aux . $i } = $d ;
615 if ( $bad { $aux } % $Wmx { $aux } > $i )
616 { $tab { $aux . $i } ++ ;
620 $tab { $aux } -= ( $W { $aux } - 1 ) * $d ;
621 $max = $tab { $aux } if $max < $tab { $aux } ;
626 # { for my $hr ( keys %tab )
627 # { printf "tab '%s' = '%s'\n", $hr, $tab { $hr } ; }
630 return 'nothing yet' unless $max ;
631 $H = $max if 8 <= $max and $max <= 26 ;
633 { $hst { $_ } = int ( $H * $tab { $_ } / $max + 0.5 ) ; }
634 my @keys = sort { $a <=> $b } grep /^\d+$/, keys %hst ;
636 for my $hr ( @keys ) { $tab_hr += $tab { $hr } ; }
638 , grep ( m/^old/, sort keys %tab )
639 , grep ( m/^ded/, sort keys %tab )
640 , grep ( m/^bad/, sort keys %tab )
642 for ( my $h = $H ; $h > 0 ; $h -- )
644 $res .= sprintf "<TH ROWSPAN=3 VALIGN=\"TOP\">↑</TH>\n"
646 $res .= sprintf '<TD ROWSPAN=%d ALIGN="CENTER">%s</TD>' . "\n"
647 , $H-6, NSS ( $max ) if $h == $H - 3 ;
648 $res .= sprintf "<TH ROWSPAN=3 VALIGN=\"BOTTOM\">↓</TH>\n"
651 { $res .= sprintf "<TH>%s</TH>\n"
652 , ( ( $hst { $x } >= $h )
656 : ( $x =~ /^old/ ? 'b' : ( $x =~ /^ded/ ? 'f' : 'z' ) )
658 : ( ( $h == 1 and $hst { $x } == 0 )
660 ( '<IMG SRC="%s/bar.gif" ALT="" BORDER=0>'
670 my $HR = '<HR SIZE=2 WIDTH="95%%" NOSHADE>' ;
673 $res .= sprintf "<TD COLSPAN=%d>$HR</TD>\n", 1 ;
674 $res .= sprintf "<TD COLSPAN=%d>$HR</TD>\n", $MAX_h ;
675 $res .= sprintf "<TD COLSPAN=%d>$HR</TD>\n", $W { old } ;
676 $res .= sprintf "<TD COLSPAN=%d>$HR</TD>\n", $W { ded } ;
677 $res .= sprintf "<TD COLSPAN=%d>$HR</TD>\n", $W { bad } ;
681 $res .= '<TD ALIGN="CENTER"> <B>age</B> → </TD>' ;
683 $res .= "<TH>|</TH>\n" ;
685 ( '<TD COLSPAN=%d ALIGN="CENTER">'
686 . '← 0 ≤ <B>age</B> ≤ %s →'
688 , $MAX_h - 2, pr_interval ( $MAX_H )
691 $res .= "<TH>|</TH>\n" ;
693 ( '<TD ALIGN="CENTER" COLSPAN=%d>'
694 . ' %sh < %s ≤ %sh '
696 , $W { old }, int($MAX_H/60/60) , BLD ( 'age' ), $MAX_o
699 ( '<TD ALIGN="CENTER" COLSPAN=%d>'
700 . ' <FONT COLOR="RED">old</FONT> '
705 ( '<TD ALIGN="CENTER" COLSPAN=%d>'
706 . ' <FONT COLOR="RED">bad</FONT> '
712 my $FRMT = '<TD ALIGN="CENTER" COLSPAN=%d> %s </TD>' ;
715 $res .= sprintf "$FRMT\n", 1, NSS scalar keys %RES ;
716 $res .= "<TH>|</TH>\n" ;
717 $res .= sprintf "$FRMT\n", $MAX_h - 2, NSS $tab_hr ;
718 $res .= "<TH>|</TH>\n" ;
719 $res .= sprintf "$FRMT\n", $W { old }, NSS $bad { old } ;
720 $res .= sprintf "$FRMT\n", $W { ded }, NSS $bad { ded } ;
721 $res .= sprintf "$FRMT\n", $W { bad }, NSS $bad { bad } ;
724 $res = "<TABLE CELLSPACING=0 CELLPADDING=1 BORDER=0>\n$res\n</TABLE>\n" ;
725 $res = sprintf "<TABLE CELLPADDING=5 BORDER=4>%s</TABLE>\n"
726 , "<TR><TH>\n$res\n</TH></TR>" ;
728 { $res .= sprintf "<BR>units %s %s %s %s represent one mirror site.\n"
729 , img_sf ( 's' ), img_sf ( 'f' ), img_sf ( 'b' ), img_sf ( 'z' ) ;
733 "<BR>each %s %s %s %s unit represents %s mirror sites.\n"
734 , img_sf ( 's' ) , img_sf ( 'f' ), img_sf ( 'b' ) , img_sf ( 'z' )
735 , sprintf ( "%.1f", $max / $H )
740 sub revdom { my $dom = shift ; join '.', reverse split /\./, $dom ; }
743 { my $a_type = $a -> [ 0 ] ;
744 my $b_type = $b -> [ 0 ] ;
745 my $a_site = $a -> [ 2 ] ;
746 my $b_site = $b -> [ 2 ] ;
747 ( revdom $a_site ) cmp ( revdom $b_site )
753 sub by_CCS { ( $CCS { $a } || $a ) cmp ( $CCS { $b } || $b ) ; }
759 my $TMP = "$PPP.tmp" ;
762 for my $url ( keys %LST )
763 { my ( $type , $site, $reg ) = @{ $LST { $url } } ;
764 push @{ $tab { $reg } }, [ $type, $url, $site ] ;
767 my $bad = 0 ; my $old = 0 ; my $unr = 0 ;
772 for my $url ( keys %RES )
773 { my ( $time, $stat, $vrfy ) = @{ $RES { $url } } ;
774 if ( $stat eq 'ok' ) { $ok ++ ; } else { $stats { $stat } ++ ; }
775 if ( $time eq 'undef' )
777 elsif ( 'f' eq age_code $time )
779 if ( $vrfy eq 'undef' or aprx_lt $vrfy, $^T - max_vrfy )
784 "%d bad -- %d older than %s -- %s unreachable for more than %s"
787 , pr_interval ( max_age2 )
789 , pr_interval ( max_vrfy )
792 my $PROB = 'last probes : ' ;
793 push @stats, "$ok were ok" if $ok ;
794 for my $stat ( sort keys %stats )
795 { push @stats, sprintf "%s had %s", $stats { $stat }, RED $stat ; }
796 $PROB .= join ', ', @stats ;
798 for my $reg ( sort keys %tab )
799 { $refs .= sprintf " %s \n"
801 , "<FONT SIZE=\"+1\">$reg</FONT>"
806 my $LOGO = $CNF { project_logo }
808 ( $CNF { project_url }
810 ( '<IMG SRC="%s" ALT="%s" ALIGN="RIGHT" BORDER=0>'
811 , $CNF { project_logo }
812 , $CNF { project_name }
817 my $HTOP = $CNF{htm_top} ? $CNF{htm_top} . "\n" : '' ;
818 my $FOOT = $CNF{htm_foot} ? $CNF{htm_foot} . "\n" : '' ;
819 my $HEAD = $CNF{htm_head} ? $CNF{htm_head} . "\n" : '' ;
820 my $TITL = url $CNF{project_url}, $CNF{project_name} ;
821 my $EXPD = exp_date ;
823 open PPP, ">$TMP" or Error "can't write $TMP ($!)" ;
824 print PPP '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01'
825 . ' Transitional//EN"'
827 print PPP "<HTML>\n" ;
828 print PPP "<HEAD>\n" ;
829 print PPP "<TITLE>the status of $CNF{project_name} mirrors</TITLE>\n" ;
830 printf PPP "%s\n", '<meta HTTP-EQUIV="content-type" '
831 . 'CONTENT="text/html; charset=ISO-8859-1">' ;
832 print PPP "<META HTTP-EQUIV=\"refresh\" CONTENT=\"3600\">\n" ;
833 print PPP "<META HTTP-EQUIV=\"Expires\" CONTENT=\"$EXPD\">\n" ;
834 print PPP $HEAD if $HEAD ;
835 print PPP "</HEAD>\n" ;
836 print PPP "<BODY BGCOLOR=\"#FFFFFF\">\n" ;
839 print PPP "<H2>the status of $TITL mirrors</H2>\n" ;
841 print PPP "<TABLE BORDER=0 CELLPADDING=2>\n" ;
842 printf PPP "<TR><TD>date</TD><TD>:</TD><TD>%s (GMT)</TD></TR>\n"
843 , scalar gmtime $^T ;
844 printf PPP "<TR><TD>last check</TD><TD>:</TD><TD>%s (GMT)</TD></TR>\n"
845 , scalar gmtime ( $opt{get} ? $^T : ( stat $CNF { state } ) [9] ) ;
846 print PPP "</TABLE>\n" ;
848 printf PPP "<P>%s</P>\n", $HTOP if $HTOP ;
850 if ( $CNF { put_histo } eq 'top' )
851 { print PPP "<H2>age histogram</H2>\n" ;
853 print PPP "<BLOCKQUOTE>\n" ;
854 print PPP gen_histogram ;
855 print PPP "</BLOCKQUOTE>\n" ;
858 print PPP "<H2>regions</H2>\n" ;
860 print PPP "<BLOCKQUOTE>\n" ;
861 print PPP "<CENTER>\n" ;
862 printf PPP "%s\n", $refs ;
863 print PPP "</CENTER>\n" ;
864 print PPP "</BLOCKQUOTE>\n" ;
866 print PPP "<H2>report</H2>\n" ;
868 my $attr1 = "COLSPAN=$COLS BGCOLOR=\"LIME\"" ;
869 my $attr2 = 'BGCOLOR="AQUA"' ;
871 print PPP "<BLOCKQUOTE>\n" ;
872 print PPP "<TABLE BORDER=2 CELLPADDING=5>\n" ;
873 printf PPP "<TR><TH $attr1>%d sites in %d regions</TH></TR>\n"
877 printf PPP "<TR><TH $attr1>%s</TH></TR>\n", $STAT ;
878 printf PPP "<TR><TH $attr1>%s</TH></TR>\n", $PROB ;
880 printf PPP " <TH $attr2>%s site -- home</TH>\n"
881 , $CNF { project_name } ;
882 printf PPP " <TH $attr2>%s</TH>\n", 'type' ;
883 printf PPP " <TH $attr2>%s</TH>\n", 'mirror age,<BR>daily stats' ;
884 printf PPP " <TH $attr2>%s</TH>\n", 'last probe,<BR>probe stats' ;
885 printf PPP " <TH $attr2>%s</TH>\n", 'last stat' ;
886 print PPP "</TR>\n" ;
887 for my $reg ( sort by_CCS keys %tab )
888 { my $itms = $tab { $reg } ;
890 my $ccs = exists $CCS { $reg } ? $CCS { $reg } : $reg ;
892 ( scalar @{ $itms } > 6
893 ? sprintf "%s - %d sites"
894 , $ccs, scalar @{ $itms }
898 my $attr3 = "COLSPAN=$COLS BGCOLOR=\"YELLOW\"" ;
899 printf PPP "<TR><TH $attr3>$ccs</TH></TR>\n" ;
901 for my $itm ( sort by_type_site @{ $itms } )
902 { my ( $type, $url, $site ) = @{ $itm } ;
903 my ( $time, $stat, $hstp, $hsts, $vrfy ) ;
904 my ( $pr_time, $pr_last, $pr_hstp, $pr_hsts ) ;
908 " <TD ALIGN=\"RIGHT\">%s %s</TD>\n"
910 , url ( $url , $site )
911 , url ( home ( $url ), '@' )
915 if ( exists $RES { $url } )
916 { ( $time, $stat, $vrfy, $hstp, $hsts ) = @{ $RES { $url } } ;
917 $pr_time = $time =~ /^\d+$/
918 ? diff $time, $^T - max_age2 : ' ' ;
919 $pr_last = $vrfy =~ /^\d+$/
920 ? diff $vrfy, $^T - max_vrfy : ' ' ;
921 $pr_hstp = show_hist $hstp ;
922 $pr_hsts = show_hist_age $hsts, $time ;
926 { ( $pr_time, $pr_last, $pr_hstp, $pr_hsts, $stat ) =
927 ( ' ', ' ', '', '', ' ' ) ;
930 $stat = RED $stat if $stat ne 'ok' ;
931 printf PPP " <TD ALIGN=\"RIGHT\">%s<BR>%s</TD>\n"
932 , $pr_time, $pr_hsts ;
933 printf PPP " <TD ALIGN=\"RIGHT\">%s<BR>%s</TD>\n"
934 , $pr_last, $pr_hstp ;
935 printf PPP " <TD>%s</TD>\n", $stat ;
936 print PPP "</TR>\n" ;
939 print PPP "</TABLE>\n" ;
940 print PPP "</BLOCKQUOTE>\n" ;
942 if ( $CNF { put_histo } eq 'bottom' )
943 { print PPP "<H2>age histogram</H2>\n" ;
945 print PPP "<BLOCKQUOTE>\n" ;
946 print PPP gen_histogram ;
947 print PPP "</BLOCKQUOTE>\n" ;
952 print PPP "<H3>probe results</H3>\n" ;
953 print PPP gen_histogram_probes ;
955 print PPP "<H3>software</H3>\n" ;
957 print PPP "<BLOCKQUOTE><TABLE><TR>\n" ;
958 my $MIR_IMG = sprintf
959 '<IMG BORDER=2 ALT="mirmon" SRC="%s/mirmon.gif">' , $CNF { icons } ;
960 print PPP sprintf "<TH><A HREF=\"%s\">%s</A></TH>\n"
961 , 'http://www.cs.uu.nl/people/henkp/mirmon/', $MIR_IMG ;
962 print PPP "<TD>$VER</TD>\n" ;
963 print PPP "</TR></TABLE></BLOCKQUOTE>\n" ;
965 print PPP "</BODY>\n" ;
966 print PPP "</HTML>" ;
968 if ( print PPP "\n" )
971 { Warn "wrote empty html file; keeping previous version" ; }
973 { rename $TMP, $PPP or Error "can't rename $TMP, $PPP ($!)" ; }
976 { Error "can't print to $TMP ($!)" ; }
983 <H4><I>project</I> site -- home</H4>
986 <B><I>project</I> site</B> is an url.
987 The <B>href</B> is the href for the site in the list of mirrors,
988 usually the root of the mirrored file tree.
989 The <B>text</B> is the <I>site</I> of that url.
991 <B>home</B> (represented by the <B>@</B>-symbol) is an url
992 pointing to the document root of the site. This pointer is
993 useful if the <B><I>project</I> site</B> url is invalid,
994 possibly because the mirror site moved the archive.
1000 Indicates the type (<B>ftp</B> or <B>http</B>) of
1001 the <B><I>project</I> site</B> and <B>home</B> urls.
1004 <H4>mirror age, daily stats</H4>
1007 The <B>mirror age</B> is based upon the last successful probe.
1009 Once a day the status of a mirror site is determined.
1010 The status (represented by a colored block) is appended
1011 to the <B>right</B> of the status history (<I>right</I>
1012 is <I>recent</I>). More precise, the status block is appended
1013 if the last status block was appended 24 (or more) hours ago.
1014 <P>The status of a mirror depends on its age and a few
1015 configuration parameters :
1017 <TABLE BORDER=1 CELLPADDING=5>
1019 <TH ROWSPAN=3>status</TH>
1020 <TH COLSPAN=4>age</TH>
1023 <TH COLSPAN=2 BGCOLOR="YELLOW">this project</TH>
1024 <TH COLSPAN=2 BGCOLOR="AQUA">in general</TH>
1027 <TH BGCOLOR="YELLOW">min</TH>
1028 <TH BGCOLOR="YELLOW">max</TH>
1029 <TH BGCOLOR="AQUA">min</TH>
1030 <TH BGCOLOR="AQUA">max</TH>
1033 <TH><FONT COLOR="GREEN">fresh</FONT></TH>
1035 <TD BGCOLOR="YELLOW" ALIGN="CENTER">0</TD>
1036 <TD BGCOLOR="YELLOW" ALIGN="CENTER">
1037 @{[$CNF{min_sync}]} + @{[$CNF{max_poll}]}</TD>
1038 <TD BGCOLOR="AQUA" ALIGN="CENTER">0</TD>
1039 <TD BGCOLOR="AQUA" ALIGN="CENTER">min_sync + max_poll</TD>
1042 <TH><FONT COLOR="BLUE">oldish</FONT></TH>
1044 <TD BGCOLOR="YELLOW" ALIGN="CENTER">
1045 @{[$CNF{min_sync}]} + @{[$CNF{max_poll}]}</TD>
1046 <TD BGCOLOR="YELLOW" ALIGN="CENTER">
1047 @{[$CNF{max_sync}]} + @{[$CNF{max_poll}]}</TD>
1048 <TD BGCOLOR="AQUA" ALIGN="CENTER">min_sync + max_poll</TD>
1049 <TD BGCOLOR="AQUA" ALIGN="CENTER">max_sync + max_poll</TD>
1052 <TH><FONT COLOR="RED">old</FONT></TH>
1054 <TD BGCOLOR="YELLOW" ALIGN="CENTER">
1055 @{[$CNF{max_sync}]} + @{[$CNF{max_poll}]}</TD>
1056 <TD BGCOLOR="YELLOW" ALIGN="CENTER">∞</TD>
1057 <TD BGCOLOR="AQUA" ALIGN="CENTER">max_sync + max_poll</TD>
1058 <TD BGCOLOR="AQUA" ALIGN="CENTER">∞</TD>
1061 <TH><FONT COLOR="BLACK">bad</FONT></TH>
1062 <TH COLSPAN=4 BGCOLOR="BLACK">
1063 <FONT COLOR="WHITE">the site or mirror tree was never found</FONT></TH>
1069 <H4>last probe, probe stats</H4>
1072 <B>Last probe</B> indicates when the last successful probe was made.
1073 <B>Probe stats</B> gives the probe history (<I>right</I> is <I>recent</I>).
1075 <FONT COLOR="GREEN"><B>success</B></FONT> or a
1076 <FONT COLOR="RED"><B>failure</B></FONT>.
1082 <B>Last stat</B> gives the status of the last probe.
1091 my $TIMEOUT = $CNF { timeout } ;
1092 my $src = $HREF { lc site $url } || $url ;
1093 $CMD =~ s/%TIMEOUT%/$TIMEOUT/g ;
1094 $CMD =~ s/%URL%/$src/g ;
1095 printf "*** SUBSTITUTE site %s\n+ url %s\n+ %s\n",
1096 site($url), $HREF { lc site $url }, $CMD
1097 if $HREF { lc site $url } and $opt{v} ;
1098 my $WGT = new IO::Pipe ;
1099 my $res = $WGT -> reader ( split ' ', $CMD ) ;
1101 { $WGT -> blocking ( 0 ) ;
1102 $GET -> add ( $WGT ) ;
1103 $URL { $WGT } = $url ;
1106 { err $url, 'no pipe' ; }
1111 my $url = $URL { $WGT } ;
1114 $WGT -> blocking ( 1 ) ;
1115 unless ( $WGT -> eof () ) { $time = $WGT -> getline () ; }
1116 $GET -> remove ( $WGT ) ;
1120 return err $url, 'no time' unless defined $time ;
1121 return err $url, "empty" if $time =~ /^\s*$/ ;
1123 $time = ( split ' ', $time ) [ 0 ] ;
1125 if ( $time !~ /^\d+$/ )
1126 { $time = htmlquote $time ;
1127 $time = substr ( $time, 0, 15 ) . '..' if length $time > 15 ;
1128 err $url, "'$time'" ;
1131 { res $url, $time, 'ok' ; }
1137 my $PAR = $CNF { max_probes } ;
1138 my $cnt_LST = scalar keys %LST ;
1139 for my $url ( sort keys %LST )
1140 { if ( $opt{get} eq 'all' or ! exists $OLD { $url } )
1141 { push @QUE, $url ; }
1142 elsif ( $opt{get} eq 'update' )
1143 { my $stat = $OLD { $url } [ 1 ] ;
1144 my $vrfy = $OLD { $url } [ 2 ] ;
1145 my $lprb = $OLD { $url } [ 5 ] ;
1146 if ( ( $lprb eq 'undef'
1147 or aprx_le $lprb, $^T - tim_to_s $CNF { min_poll }
1150 or aprx_le $vrfy, $^T - tim_to_s $CNF { max_poll }
1153 { push @QUE, $url ; }
1154 elsif ( $CNF { randomize } and 0 == int rand $cnt_LST )
1155 { push @QUE, $url ; }
1157 { $RES { $url } = $OLD { $url } ; }
1160 { Error "unknown opt_get '$opt{get}'" ; }
1164 { while ( $GET -> count () < $PAR and @QUE )
1165 { my $url = shift @QUE ;
1166 if ( gethost site $url )
1167 { start_date $url, $CMD ; }
1169 { err $url, 'site not found' ; }
1172 my @can_read = $GET -> can_read ( 0 ) ;
1174 printf "que %d, get %d, can %d\n",
1175 scalar @QUE, $GET -> count (), scalar @can_read
1178 for my $can_read ( @can_read )
1179 { get_date $can_read ; }
1184 my $stop = time + $CNF { timeout } + 10 ;
1186 while ( $GET -> count () and time < $stop )
1189 my @can_read = $GET -> can_read ( 0 ) ;
1191 printf "wait %2d, get %d, can %d\n",
1192 $stop - scalar time, $GET -> count (), scalar @can_read
1195 for my $can_read ( @can_read )
1196 { get_date $can_read ; }
1199 for my $WGT ( $GET -> handles () )
1200 { my $url = $URL { $WGT } ;
1206 get_ccs $CNF { countries } ;
1207 get_state $CNF { state } ;
1208 get_list $CNF { mirror_list } ;
1211 { get_dates $CNF { probe } ;
1212 put_state $CNF { state } ;
1217 gen_page $CNF { web_page } ;
1225 mirmon - monitor the state of mirrors
1229 mirmon [ -v ] [ -q ] [ -t timeout ] [ -get opt ] [ -c conf ]
1233 option v : be verbose
1235 option t : set timeout [ default 300 ] ;
1236 option get : 'all' : probe all sites
1237 : 'update' : probe a selection of the sites (see doc)
1238 option c : configuration file ; default list :
1239 ./mirmon.conf $HOME/.mirmon.conf /etc/mirmon.conf
1240 -------------------------------------------------------------------
1241 Documentation : the program contains 'pod' style documentation.
1242 Extract the doc with 'pod2text mirmon' or 'pod2html mirmon OUT', etc.
1243 -------------------------------------------------------------------
1247 The program is intended to be run by cron every hour.
1249 42 * * * * perl /path/to/mirmon -q -get update
1251 It quietly probes a subset of the sites in a given list,
1252 writes the results in the 'state' file and generates a web page
1253 with the results. The subset contains the sites that are new, bad
1254 and/or not probed for a specified time.
1256 When no 'get' option is specified, the program just generates a
1257 new web page from the last known state.
1259 The program checks the mirrors by running a (user specified)
1260 program on a pipe. A (user specified) number of probes is
1261 run in parallel using nonblocking IO. When something can be
1262 read from the pipe, it switches the pipe to blocking IO and
1263 reads one line from the pipe. Then it flushes and closes the
1264 pipe. No attempt is made to kill the probe.
1266 The probe should return something that looks like "1043625600\n",
1267 that is, a timestamp followed by a newline. The exit status of
1268 the probe is ignored.
1274 A config file can be specified with the -c option.
1275 If -c is not used, the program looks for a config file in
1277 -- $HOME/.mirmon.conf
1282 A config file looks like this :
1284 +--------------------------------------------------
1285 |# lines that start with '#' are comment
1286 |# blank lines are ignored too
1287 |# tabs are replaced by a space
1289 |# the config entries are 'key' and 'value' pairs
1290 |# a 'key' begins in column 1
1291 |# the 'value' is the rest of the line
1292 |somekey A_val B_val ...
1293 |otherkey X_val Y_val ...
1295 |# indented lines are glued
1296 |# the next three lines mean 'somekey part1 part2 part3'
1301 |# lines starting with a '+' are concatenated
1302 |# the next three lines mean 'somekey part1part2part3'
1307 |# lines starting with a '.' are glued too
1308 |# don't use a '.' on a line by itself
1309 |# 'somekey' gets the value "part1\n part2\n part3"
1313 +--------------------------------------------------
1315 =head1 CONFIG FILE : required entries
1317 =head2 project_name <name>
1319 Specify a short plaintext name for the project.
1324 =head2 project_url <url>
1326 Specify an url pointing to the 'home' of the project.
1328 project_url http://www.apache.org/
1330 =head2 mirror_list <file name>
1332 Specify the file containing the mirrors to probe.
1333 Two formats are supported :
1335 -- plain : lines like
1337 us http://www.tux.org/
1338 nl http://apache.cs.uu.nl/dist/
1340 -- apache : lines like those in the apache mirrors.list
1342 ftp us ftp://ftp.tux.org/pub/net/apache/dist/ user@tux.org
1343 http nl http://apache.cs.uu.nl/dist/ user@cs.uu.nl
1345 Specify the required format with 'list_style' (see below).
1346 The default style is 'plain'.
1348 If the url part of a line doesn't end in a slash ('/'), mirmon
1349 adds a slash and issues a warning unless it is in quiet mode.
1351 =head2 web_page <file name>
1353 Specify where the html report page is written.
1355 =head2 icons <directory name>
1357 Specify the directory where the icons can be found.
1359 =head2 probe <program + arguments>
1361 Specify the program+args to probe the mirrors. Example:
1363 probe /sw/bin/wget -q -O - -T %TIMEOUT% -t 1 %URL%TIME
1365 Before the program is started, %TIMEOUT% and %URL% are
1366 substituted with the proper timeout and url values.
1368 Here it is assumed that each hour the root server writes
1369 a timestamp in /path/to/archive/TIME, for instance with
1370 a crontab entry like
1372 42 * * * * perl -e 'printf "%s\n", time' > /path/to/archive/TIME
1374 Mirmon reads one line of output from the probe and interprets
1375 the first word on that line as a timestamp ; for example :
1378 1043625600 Mon Jan 27 00:00:00 2003
1379 1043625600 www.apache.org Mon Jan 27 00:00:00 2003
1381 =head2 state <file name>
1383 Specify where the file containing the state is written.
1384 The program reads this file on startup and writes the
1385 file when mirrors are probed (-get is specified).
1387 =head2 countries <file name>
1389 Specify the file containing the country codes;
1390 The file should contain lines like
1395 The mirmon package contains a recent ISO list.
1397 =head1 CONFIG FILE : optional entries
1399 =head2 max_probes <number>
1401 Optionally specify the number of parallel probes (default 25).
1403 =head2 timeout <seconds>
1405 Optionally specify the timeout for the probes (default 300).
1406 After the last probe is started, the program waits for
1407 <timeout> + 10 seconds, cleans up and exits.
1409 =head2 project_logo <logo>
1411 Optionally specify (the SRC of the IMG of) a logo to be placed
1412 top right on the page.
1414 project_logo /icons/apache.gif
1415 project_logo http://www.apache.org/icons/...
1417 =head2 htm_head <html>
1419 Optionally specify some HTML to be placed before </HEAD>.
1422 <link REL=StyleSheet HREF="/style.css" TYPE="text/css">
1424 =head2 htm_top <html>
1426 Optionally specify some HTML to be placed near the top of the page.
1427 The supplied text is placed between <P> and </P>.
1429 htm_top testing 1, 2, 3
1431 =head2 htm_foot <html>
1433 Optionally specify HTML to be placed near the bottom of the page.
1437 <A HREF="..."><IMG SRC="..." BORDER=0></A>
1440 =head2 put_histo top|bottom|nowhere
1442 Optionally specify where the age histogram must be placed.
1443 The default is 'top'.
1445 =head2 min_poll <time spec>
1447 For 'min_poll' see next item. A <time spec> is a number followed by
1448 a unit 's' (seconds), or 'm' (minutes), or 'h' (hours), or 'd' (days).
1449 For example '3d' (three days) or '36h' (36 hours).
1451 =head2 max_poll <time spec>
1453 Optionally specify the maximum probe interval. When the program is
1454 called with option '-get update', all sites are probed which are :
1455 -- new : the site appears in the list, but there is no known state
1456 -- bad : the last probe of the site was unsuccessful
1457 -- old : the last probe was more than 'max_poll' ago.
1458 Sites are not probed if the last probe was less than 'min_poll' ago.
1465 the 'reachable' sites are probed twice daily and the 'unreachable'
1466 sites are probed at most six times a day.
1468 The default 'min_poll' is '1h' (1 hour).
1469 The default 'max_poll' is '4h' (4 hours).
1471 =head2 min_sync <time spec>
1473 Optionally specify how often the mirrors are required to
1474 make an update. The default 'min_sync' is '1d' (1 day).
1476 =head2 max_sync <time spec>
1478 Optionally specify the maximum allowable sync interval.
1479 Sites exceeding the limit will be considered 'old'.
1480 The default 'max_sync' is '2d' (2 days).
1484 With a low probablility, mirmon probes mirrors that would
1485 otherwise not be probed. In the long run, this balances
1486 the number of mirror probes over the hourly mirmon runs.
1487 Specifically, if there are N mirrors in the list and some
1488 mirmon run would probe K sites, on average (N-K)/N extra
1489 sites will be probed.
1491 If you don't want this behaviour, use 'no_randomize'.
1493 =head2 list_style plain|apache
1495 Optionally specify the format ('plain' or 'apache') of the
1496 mirror-list. See the description of 'mirror_list' above.
1497 The default list_style is 'plain'.
1499 =head2 site_url <site> <url>
1501 Optionally specify a substitute url for a site. When access to
1502 a site is restricted (in Australia, for instance), another
1503 (sometimes secret) url can be used to probe the site. The <site>
1504 of an url is the part between '://' and the first '/'.
1506 =head2 env <key> <value>
1508 Optionally specify an environment variable.
1510 =head2 include <file name>
1512 Optionally specify a file to include. The specified file is processed
1513 'in situ'. After the specified file is read and processed, config
1514 processing is resumed in the file where the 'include' was encountered.
1515 The 'include' depth is unlimited. However, it is a fatal error to
1516 include a file twice under the same name.
1520 When the config processor encounters the 'show' command, it
1521 dumps the content of the current config to standout, if option
1522 -v is specified. This is intented for debugging.
1526 When the config processor encounters the 'exit' command, it
1527 terminates the program. This is intented for debugging.
1529 =head1 STATE FILE FORMAT
1531 The state file consists of lines; one line per site.
1532 Each line consists of white space separated fields.
1533 The seven fields are :
1535 =head2 field 1 : url
1537 The url as given in the mirror list.
1539 =head2 field 2 : age
1541 The age of the site, or 'undef' if no probe was ever successful.
1543 =head2 field 3 : status last probe
1545 The status of the last probe.
1547 =head2 field 4 : time last succesful probe
1549 The timestamp of the last succesful probe or 'undef'
1550 if the site was never successfully probed.
1552 =head2 field 5 : probe history
1554 The probe history is a list of 's' (for success) and 'f' (for failure)
1555 characters indicating the result of the probe. New results are appended
1556 whenever the site is probed.
1558 =head2 field 6 : state history
1560 The state history consists of a timestamp, a '-' char, and a list of
1561 chars indicating a past status: 's' (fresh), 'b' (oldish), 'f' (old)
1562 or 'z' (bad). The timestamp indicates when the state history was last
1563 updated. The state history is updated when the state file is updated
1564 and the last update of the history state was 24 (or more) hours ago.
1565 The status is determined by the site's age and a few configuration
1566 parameters. The details are explained in the legend of the report page.
1568 =head2 field 7 : last probe
1570 The timestamp of the last probe.
1578 The '#!' path for perl is probably wrong.
1588 <A HREF="http://www.cs.uu.nl/staff/henkp.html">Henk P. Penning</A>,
1589 <A HREF="http://www.cs.uu.nl/">Computer Science Department</A>,
1590 <A HREF="http://www.uu.nl/">Utrecht University</A>
1592 $Id: mirmon,v 1.37 2006/12/04 15:16:11 henkp Exp henkp $
1599 (c) 2003 Henk P. Penning, Computer Science Department, Utrecht University
1600 http://www.cs.uu.nl/staff/henkp.html -- penning@cs.uu.nl