2 # Copyright (c) The Exim Maintainers 2022
3 # SPDX-License-Identifier: GPL-2.0-or-later
7 BEGIN { pop @INC if $INC[-1] eq '.' };
11 usage: ratelimit.pl [options] <period> <regex> <logfile>
13 The aim of this script is to compute clients' peak sending rates
14 from an Exim log file, using the same formula as Exim's ratelimit
15 ACL condition. This is so that you can get an idea of a reasonable
16 limit setting before you deploy the restrictions.
20 -d Show debugging information to stderr
21 -p Show progress of parse the log to stderr
23 <period> The smoothing period in seconds, as defined by the
24 documentation for the ratelimit ACL condition.
26 This script isn't perfectly accurate, because the time
27 stamps in Exim's log files are only accurate to a second
28 whereas internally Exim computes sender rates to the
29 accuracy of your computer's clock (typically 10ms).
31 <regex> The second argument is a regular expression.
33 Each line is matched against the regular expression.
34 Lines that do not match are ignored. The regex may
35 contain 0, 1, or 2 () capturing sub-expressions.
37 If there are no () sub-expressions, then every line that
38 matches is used to compute a single rate. Its maximum
39 value is reported when the script finishes.
41 If there is one () sub-expression, then the text matched
42 by the sub-expression is used to identify a rate lookup
43 key, similar to the lookup key used by the ratelimit
44 ACL condition. For example, you might write a regex
45 to match the client IP address, or the authenticated
46 username. Separate rates are computed for each different
47 client and the maximum rate for each client is reported
48 when the script finishes.
50 If there are two () sub-expressions, then the text matched
51 by the first sub-expression is used to identify a rate
52 lookup key as above, and the second is used to match the
53 message size recorded in the log line, e.g. "S=(\\d+)".
54 In this case the byte rate is computed instead of the
55 message rate, similar to the per_byte option of the
56 ratelimit ACL condition.
58 <logfile> The log files to be processed can be specified on the
59 command line after the other arguments; if no filenames
60 are specified the script will read from stdin.
64 ./ratelimit.pl 1 ' <= .*? \[(.*?)\]' <logfile>
66 Compute burst sending rate like ACL condition
67 ratelimit = 0 / 1s / strict / \$sender_host_address
69 ./ratelimit.pl 3600 '<= (.*?) ' <logfile>
71 Compute sending rate like ACL condition
72 ratelimit = 0 / 1h / strict / \$sender_address
79 my ($y,$m,$d,$H,$M,$S,$zs,$zh,$zm) = @_;
82 $m += $m < 3 ? 10 : -2;
83 my $z = defined $zs ? "${zs}1" * ($zh * 60 + $zm) : 0;
84 my $t = $y/400 - $y/100 + $y/4 + $y*365
85 + $m*367/12 + $d - 719499;
95 while (@ARGV && $ARGV[0] =~ /^-\w+$/) {
96 $debug = 1 if $ARGV[0] =~ s/(-\w*)d(\w*)/$1$2/;
97 $progress = 1 if $ARGV[0] =~ s/(-\w*)p(\w*)/$1$2/;
98 shift if $ARGV[0] eq "-";
108 my $re = qr{$re_txt}o;
116 printf STDERR "%s\t%12d %8s %5.2f %5.2f\n",
117 $_, $time{$key}, $key, $max{$key}, $rate{$key};
121 next unless $_ =~ $re;
123 my $size = $2 || 1.0;
125 ($_ =~ m{^(\d{4})-(\d\d)-(\d\d)[ ]
126 (\d\d):(\d\d):(\d\d)[ ]
127 (?:([+-])(\d\d)(\d\d)[ ])?
130 my $prog_now = substr $_, 0, 14;
131 if ($progtime ne $prog_now) {
132 $progtime = $prog_now;
133 print STDERR "$progtime\n";
136 if (not defined $time{$key}) {
140 debug $key if $debug;
143 # see acl_ratelimit() for details of the following
144 my $interval = $time - $time{$key};
145 $interval = 1e-9 if $interval <= 0.0;
146 my $i_over_p = $interval / $period;
147 my $a = exp(-$i_over_p);
149 $rate{$key} = $size * (1.0 - $a) / $i_over_p + $a * $rate{$key};
150 $max{$key} = $rate{$key} if $rate{$key} > $max{$key};
151 debug $key if $debug;
155 " " x (20 - length) .
158 $max{$a} <=> $max{$b}