Add missing changelog entries.
[mono.git] / mono / tests / stress-runner.pl
1 #!/usr/bin/perl -w
2
3 # mono stress test tool
4 # This stress test runner is designed to detect possible
5 # leaks, runtime slowdowns and crashes when a task is performed
6 # repeatedly.
7 # A stress program should be written to repeat for a number of times
8 # a specific task: it is run a first time to collect info about memory
9 # and cpu usage: this run should last a couple of seconds or so.
10 # Then, the same program is run with a number of iterations that is at least
11 # 2 orders of magnitude bigger than the first run (3 orders should be used,
12 # eventually, to detect smaller leaks).
13 # Of course the right time for the test and the ratio depends on the test
14 # itself, so it's configurable per-test.
15 # The test driver will then check that the second run has used roughly the
16 # same amount of memory as the first and a proportionally bigger cpu time.
17 # Note: with a conservative GC there may be more false positives than
18 # with a precise one, because heap size may grow depending on timing etc.
19 # so failing results need to be checked carefully. In some cases a solution
20 # is to increase the number of runs in the dry run.
21
22 use POSIX ":sys_wait_h";
23 use Time::HiRes qw(usleep ualarm gettimeofday tv_interval);
24
25 # in milliseconds between checks of resource usage
26 my $interval = 50;
27 # multiplier to allow some wiggle room
28 my $wiggle_ratio = 1.05;
29 # if the test computer is too fast or if we want to stress test more,
30 # we multiply the test ratio by this number. Use the --times=x option.
31 my $extra_strong = 1;
32
33 # descriptions of the tests to run
34 # for each test:
35 #       program is the program to run
36 #       args an array ref of argumenst to pass to program
37 #       arg-knob is the index of the argument in args that changes the number of iterations
38 #       ratio is the multiplier applied to the arg-knob argument
39 my %tests = (
40         'domain-stress' => {
41                 'program' => 'domain-stress.exe',
42                 # threads, domains, allocs, loops
43                 'args' => [2, 10, 1000, 1],
44                 'arg-knob' => 3, # loops
45                 'ratio' => 30,
46         },
47         'gchandle-stress' => {
48                 'program' => 'gchandle-stress.exe',
49                 # allocs, loops
50                 'args' => [80000, 2],
51                 'arg-knob' => 1, # loops
52                 'ratio' => 20,
53         },
54         'monitor-stress' => {
55                 'program' => 'monitor-stress.exe',
56                 # loops
57                 'args' => [10],
58                 'arg-knob' => 0, # loops
59                 'ratio' => 20,
60         },
61         'gc-stress' => {
62                 'program' => 'gc-stress.exe',
63                 # loops
64                 'args' => [25],
65                 'arg-knob' => 0, # loops
66                 'ratio' => 20,
67         },
68         'thread-stress' => {
69                 'program' => 'thread-stress.exe',
70                 # loops
71                 'args' => [20],
72                 'arg-knob' => 0, # loops
73                 'ratio' => 20,
74         },
75         'abort-stress-1' => {
76                 'program' => 'abort-stress-1.exe',
77                 # loops,
78                 'args' => [20],
79                 'arg-knob' => 0, # loops
80                 'ratio' => 20,
81         },
82         # FIXME: This tests exits, so it has no loops, instead it should be run more times
83         'exit-stress' => {
84                 'program' => 'exit-stress.exe',
85                 # loops,
86                 'args' => [10],
87                 'arg-knob' => 0, # loops
88                 'ratio' => 20,
89         }
90         # FIXME: This test deadlocks, bug 72740.
91         # We need hang detection
92         #'abort-stress-2' => {
93         #       'program' => 'abort-stress-2.exe',
94         #       # loops,
95         #       'args' => [20],
96         #       'arg-knob' => 0, # loops
97         #       'ratio' => 20,
98         #}
99 );
100
101 # poor man option handling
102 while (@ARGV) {
103         my $arg = shift @ARGV;
104         if ($arg =~ /^--times=(\d+)$/) {
105                 $extra_strong = $1;
106                 next;
107         }
108         if ($arg =~ /^--interval=(\d+)$/) {
109                 $interval = $1;
110                 next;
111         }
112         unshift @ARGV, $arg;
113         last;
114 }
115 my $test_rx = shift (@ARGV) || '.';
116 # the mono runtime to use and the arguments to pass to it
117 my @mono_args = @ARGV;
118 my @results = ();
119 my %vmmap = qw(VmSize 0 VmLck 1 VmRSS 2 VmData 3 VmStk 4 VmExe 5 VmLib 6 VmHWM 7 VmPTE 8 VmPeak 9);
120 my @vmnames = sort {$vmmap{$a} <=> $vmmap{$b}} keys %vmmap;
121 # VmRSS depends on the operating system's decisions
122 my %vmignore = qw(VmRSS 1);
123 my $errorcount = 0;
124 my $numtests = 0;
125
126 @mono_args = 'mono' unless @mono_args;
127
128 apply_options ();
129
130 foreach my $test (sort keys %tests) {
131         next unless ($tests{$test}->{'program'} =~ /$test_rx/);
132         $numtests++;
133         run_test ($test, 'dry');
134         run_test ($test, 'stress');
135 }
136
137 # print all the reports at the end
138 foreach my $test (sort keys %tests) {
139         next unless ($tests{$test}->{'program'} =~ /$test_rx/);
140         print_test_report ($test);
141 }
142
143 print "No tests matched '$test_rx'.\n" unless $numtests;
144
145 if ($errorcount) {
146         print "Total issues: $errorcount\n";
147         exit (1);
148 } else {
149         exit (0);
150 }
151
152 sub run_test {
153         my ($name, $mode) = @_;
154         my $test = $tests {$name};
155         my @targs = (@mono_args, $test->{program});
156         my @results = ();
157         my @rargs = @{$test->{"args"}};
158
159         if ($mode ne "dry") {
160                 # FIXME: set also a timeout
161                 $rargs [$test->{"arg-knob"}] *= $test->{"ratio"};
162         }
163         push @targs, @rargs;
164         print "Running test '$name' in $mode mode\n";
165         my $start_time = [gettimeofday];
166         my $pid = fork ();
167         if ($pid == 0) {
168                 exec @targs;
169                 die "Cannot exec: $! (@targs)\n";
170         } else {
171                 my $kid;
172                 do {
173                         $kid = waitpid (-1, WNOHANG);
174                         my $sample = collect_memusage ($pid);
175                         push @results, $sample if (defined ($sample) && @{$sample});
176                         # sleep for a few ms
177                         usleep ($interval * 1000) unless $kid > 0;
178                 } until $kid > 0;
179         }
180         my $end_time = [gettimeofday];
181         $test->{"$mode-cputime"} = tv_interval ($start_time, $end_time);
182         $test->{"$mode-memusage"} = [summarize_result (@results)];
183 }
184
185 sub print_test_report {
186         my ($name) = shift;
187         my $test = $tests {$name};
188         my ($cpu_dry, $cpu_test) = ($test->{'dry-cputime'}, $test->{'stress-cputime'});
189         my @dry_mem = @{$test->{'dry-memusage'}};
190         my @test_mem = @{$test->{'stress-memusage'}};
191         my $ratio = $test->{'ratio'};
192         print "Report for test: $name\n";
193         print "Cpu usage: dry: $cpu_dry, stress: $cpu_test\n";
194         print "Memory usage (KB):\n";
195         print "\t       ",join ("\t", @vmnames), "\n";
196         print "\t   dry: ", join ("\t", @dry_mem), "\n";
197         print "\tstress: ", join ("\t", @test_mem), "\n";
198         if ($cpu_test > ($cpu_dry * $ratio) * $wiggle_ratio) {
199                 print "Cpu usage not proportional to ratio $ratio.\n";
200                 $errorcount++;
201         }
202         my $i;
203         for ($i = 0; $i < @dry_mem; ++$i) {
204                 next if exists $vmignore {$vmnames [$i]};
205                 if ($test_mem [$i] > $dry_mem [$i] * $wiggle_ratio) {
206                         print "Memory usage $vmnames[$i] not constant.\n";
207                         $errorcount++;
208                 }
209         }
210 }
211
212 sub collect_memusage {
213         my ($pid) = @_;
214         open (PROC, "</proc/$pid/status") || return undef; # might be dead already
215         my @sample = ();
216         while (<PROC>) {
217                 next unless /^(Vm.*?):\s+(\d+)\s+kB/;
218                 $sample [$vmmap {$1}] = $2;
219         }
220         close (PROC);
221         return \@sample;
222 }
223
224 sub summarize_result {
225         my (@data) = @_;
226         my (@result) = (0) x 7;
227         my $i;
228         foreach my $sample (@data) {
229                 for ($i = 0; $i < 7; ++$i) {
230                         if ($sample->[$i] > $result [$i]) {
231                                 $result [$i] = $sample->[$i];
232                         }
233                 }
234         }
235         return @result;
236 }
237
238 sub apply_options {
239         foreach my $test (values %tests) {
240                 $test->{args}->[$test->{'arg-knob'}] *= $extra_strong;
241         }
242 }
243