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