Fix the handling of reverse time in the ratelimit code.
[exim.git] / src / util / ratelimit.pl
CommitLineData
7546de58
TF
1#!/usr/bin/perl -wT
2#
e5d5a95f 3# $Cambridge: exim/src/util/ratelimit.pl,v 1.2 2005/09/13 18:06:31 fanf2 Exp $
7546de58
TF
4
5use strict;
6
7sub usage () {
8 print <<END;
9usage: ratelimit.pl <period> <regex> logfile
10
11The aim of this script is to compute clients' peak sending rates
12from an Exim log file, using the same formula as Exim's ratelimit
13ACL condition. This is so that you can get an idea of a reasonable
14limit setting before you deploy the restrictions.
15
16This script isn't perfectly accurate, because the time stamps in
17Exim's log files are only accurate to a seond whereas internally Exim
18computes sender rates to the accuracy of your computer's clock
19(typically 10ms).
20
21The log files to be processed can be specified on the command line
22after the other arguments; if no filenames are specified the script
23will read from stdin.
24
25The first command line argument is the smoothing period, as defined by
26the documentation for the ratelimit ACL condition. The second argumetn
27is a regular expression.
28
29Each line is matched against the regular expression. Lines that do not
30match are ignored. The regex may contain 0, 1, or 2 () capturing
31sub-expressions.
32
33If there are no () sub-expressions, then every line that matches is
34used to compute a single rate. Its maximum value is reported when the
35script finishes.
36
37If there is one () sub-expression, then the text matched by the
38sub-expression is used to identify a rate lookup key, similar to the
39lookup key used by the ratelimit ACL condition. For example, you might
40write a regex to match the client IP address, or the authenticated
41username. Separate rates are computed for each different client and
42the maximum rate for each client is reported when the script finishes.
43
44If there are two () sub-expressions, then the text matched by the
45first sub-expression is used to identify a rate lookup key as above,
46and the second is used to match the message size recorded in the log
47line, e.g. " S=(\\d+) ". In this case the byte rate is computed instead
48of the message rate, similar to the per_byte option of the ratelimit
49ACL condition.
50END
51 exit 1;
52}
53
54sub iso2unix (@) {
55 my ($y,$m,$d,$H,$M,$S,$zs,$zh,$zm) = @_;
56 use integer;
57 $y -= $m < 3;
58 $m += $m < 3 ? 10 : -2;
59 my $z = defined $zs ? "${zs}1" * ($zh * 60 + $zm) : 0;
60 my $t = $y/400 - $y/100 + $y/4 + $y*365
61 + $m*367/12 + $d - 719499;
62 return $t * 86400
63 + $H * 3600
64 + $M * 60
65 + $S
66 - $z;
67}
68
69my $debug = 0;
70my $progress = 0;
71while (@ARGV && $ARGV[0] =~ /^-\w+$/) {
72 $debug = 1 if $ARGV[0] =~ s/(-\w*)d(\w*)/$1$2/;
73 $progress = 1 if $ARGV[0] =~ s/(-\w*)p(\w*)/$1$2/;
74 shift if $ARGV[0] eq "-";
75}
76
77usage if @ARGV < 2;
78
79my $progtime = "";
80
81my $period = shift;
82
83my $re_txt = shift;
84my $re = qr{$re_txt}o;
85
86my %time;
87my %rate;
88my %max;
89
90sub debug ($) {
91 my $key = shift;
92 printf "%s\t%12d %8s %5.2f %5.2f\n",
93 $_, $time{$key}, $key, $max{$key}, $rate{$key};
94}
95
96while (<>) {
97 next unless $_ =~ $re;
98 my $key = $1 || "";
99 my $size = $2 || 1.0;
100 my $time = iso2unix
101 ($_ =~ m{^(\d{4})-(\d\d)-(\d\d)[ ]
102 (\d\d):(\d\d):(\d\d)[ ]
103 (?:([+-])(\d\d)(\d\d)[ ])?
104 }x);
105 if ($progress) {
106 my $prog_now = substr $_, 0, 14;
107 if ($progtime ne $prog_now) {
108 $progtime = $prog_now;
109 print "$progtime\n";
110 }
111 }
112 if (not defined $time{$key}) {
113 $time{$key} = $time;
114 $rate{$key} = 0.0;
115 $max{$key} = 0.0;
116 debug $key if $debug;
117 next;
118 }
119 # see acl_ratelimit() for details of the following
120 my $interval = $time - $time{$key};
e5d5a95f 121 $interval = 1e-9 if $interval <= 0.0;
7546de58
TF
122 my $i_over_p = $interval / $period;
123 my $a = exp(-$i_over_p);
7546de58
TF
124 $time{$key} = $time;
125 $rate{$key} = $size * (1.0 - $a) / $i_over_p + $a * $rate{$key};
126 $max{$key} = $rate{$key} if $rate{$key} > $max{$key};
127 debug $key if $debug;
128}
129
130print map {
131 " " x (20 - length) .
132 "$_ : $max{$_}\n"
133} sort {
134 $max{$a} <=> $max{$b}
135} keys %max;
136
137# eof