Added some stress tests and test driver for them.
authorPaolo Molaro <lupus@oddwiz.org>
Mon, 21 Feb 2005 09:15:02 +0000 (09:15 -0000)
committerPaolo Molaro <lupus@oddwiz.org>
Mon, 21 Feb 2005 09:15:02 +0000 (09:15 -0000)
svn path=/trunk/mono/; revision=40972

mono/tests/Makefile.am
mono/tests/domain-stress.cs [new file with mode: 0644]
mono/tests/gc-stress.cs [new file with mode: 0644]
mono/tests/gchandle-stress.cs [new file with mode: 0644]
mono/tests/monitor-stress.cs [new file with mode: 0644]
mono/tests/stress-runner.pl [new file with mode: 0755]
mono/tests/thread-stress.cs [new file with mode: 0644]

index dcf9ec06c59f04842f1a49c3f8079005391c90e6..ed15067b10c21836555e5385417185912520a659 100644 (file)
@@ -15,6 +15,13 @@ ILASM = $(RUNTIME) $(mcs_topdir)/ilasm/ilasm.exe
 
 BENCHSRC=fib.cs random.cs nested-loops.cs ackermann.cs tight-loop.cs sieve.cs
 
+STRESS_TESTS_SRC=      \
+       domain-stress.cs        \
+       gchandle-stress.cs      \
+       monitor-stress.cs       \
+       thread-stress.cs        \
+       gc-stress.cs
+
 TEST_CS_SRC=                   \
        array-init.cs           \
        arraylist.cs            \
@@ -235,8 +242,9 @@ TEST_IL_SRC=                        \
 TESTSI_CS=$(TEST_CS_SRC:.cs=.exe)
 TESTSI_IL=$(TEST_IL_SRC:.il=.exe)
 TESTBS=$(BENCHSRC:.cs=.exe)
+STRESS_TESTS=$(STRESS_TESTS_SRC:.cs=.exe)
 
-EXTRA_DIST=test-driver $(TEST_CS_SRC) $(TEST_IL_SRC) $(BENCHSRC)
+EXTRA_DIST=test-driver $(TEST_CS_SRC) $(TEST_IL_SRC) $(BENCHSRC) $(STRESS_TESTS_SRC) stress-runner.pl
 
 %.exe: %.il
        $(ILASM) -out:$@ $<
@@ -349,6 +357,24 @@ testjitspeed: $(JITTEST_PROG) $(TESTBS)
                time $(JITTEST_PROG) $$i;       \
        done
 
+stresstest: $(STRESS_TESTS)
+       @failed=0; \
+       passed=0; \
+       failed_tests="";\
+       for i in $(STRESS_TESTS); do    \
+               if $(srcdir)/stress-runner.pl $$i ../mini/mono $(RUNTIME_ARGS); \
+               then \
+                       passed=`expr $${passed} + 1`; \
+               else \
+                       if [ $$? = 2 ]; then break; fi; \
+                       failed=`expr $${failed} + 1`; \
+                       failed_tests="$${failed_tests} $$i"; \
+               fi \
+       done; \
+       echo "$${passed} test(s) passed. $${failed} test(s) failed."; \
+       if [ $${failed} != 0 ]; then echo -e "\nFailed tests:\n"; \
+         for i in $${failed_tests}; do echo $${i}; done; exit 1; fi
+
 noinst_LTLIBRARIES = libtest.la
 
 INCLUDES = $(GLIB_CFLAGS)
@@ -363,4 +389,4 @@ endif
 libtest_la_SOURCES = libtest.c
 libtest_la_LIBADD = $(GLIB_LIBS)
 
-CLEANFILES = $(TESTSI_CS) $(TESTSI_IL) *.dll *.stdout *.exe stest.dat
+CLEANFILES = $(TESTSI_CS) $(TESTSI_IL) $(STRESS_TESTS) *.dll *.stdout *.exe stest.dat
diff --git a/mono/tests/domain-stress.cs b/mono/tests/domain-stress.cs
new file mode 100644 (file)
index 0000000..7bf3539
--- /dev/null
@@ -0,0 +1,68 @@
+using System;
+using System.Threading;
+using System.Collections;
+
+class T {
+       /* each thread will create n domains */
+       static int threads = 5;
+       static int domains = 100;
+       static int allocs = 1000;
+       static int loops = 1;
+       static int errors = 0;
+
+       public static void worker () {
+               Console.WriteLine ("Domain start " + AppDomain.CurrentDomain.FriendlyName + " " + Thread.CurrentThread.GetHashCode ());
+               ArrayList list = new ArrayList ();
+               for (int i = 0; i < allocs; ++i) {
+                       list.Add (new object ());
+                       list.Add (new ArrayList ());
+                       list.Add (new String ('x', 34));
+                       int[] a = new int [5];
+                       list.Add (new WeakReference (a));
+                       if ((i % 1024) == 0) {
+                               list.RemoveRange (0, list.Count / 2);
+                       }
+               }
+               Console.WriteLine ("Domain end " + AppDomain.CurrentDomain.FriendlyName + " " + Thread.CurrentThread.GetHashCode ());
+       }
+
+       static void thread_start () {
+               Console.WriteLine ("Thread start " + Thread.CurrentThread.GetHashCode ());
+               for (int i = 0; i < domains; ++i) {
+                       AppDomain appDomain = AppDomain.CreateDomain("Test-" + i);
+                       appDomain.DoCallBack (new CrossAppDomainDelegate (worker));
+                       try {
+                               AppDomain.Unload (appDomain);
+                       } catch {
+                               Interlocked.Increment (ref errors);
+                               Console.WriteLine ("Error unloading " + "Test-" + i);
+                       }
+               }
+               Console.WriteLine ("Thread end " + Thread.CurrentThread.GetHashCode ());
+       }
+       static int Main (string[] args) {
+               if (args.Length > 0)
+                       threads = int.Parse (args [0]);
+               if (args.Length > 1)
+                       domains = int.Parse (args [1]);
+               if (args.Length > 2)
+                       allocs = int.Parse (args [2]);
+               if (args.Length > 3)
+                       loops = int.Parse (args [3]);
+               for (int j = 0; j < loops; ++j) {
+                       Thread[] ta = new Thread [threads];
+                       for (int i = 0; i < threads; ++i) {
+                               Thread t = new Thread (new ThreadStart (thread_start));
+                               ta [i] = t;
+                               t.Start ();
+                       }
+                       for (int i = 0; i < threads; ++i) {
+                               ta [i].Join ();
+                       }
+               }
+               //thread_start ();
+               //Console.ReadLine ();
+               return 0;
+       }
+}
+
diff --git a/mono/tests/gc-stress.cs b/mono/tests/gc-stress.cs
new file mode 100644 (file)
index 0000000..780397e
--- /dev/null
@@ -0,0 +1,26 @@
+using System;
+
+class T {
+       
+       static int count = 1000000;
+       static int loops = 20;
+       static object obj;
+       static object obj2;
+
+       static void work () {
+               for (int i = 0; i < count; ++i) {
+                       obj = new object ();
+                       obj2 = i;
+               }
+       }
+       static void Main (string[] args) {
+               if (args.Length > 0)
+                       loops = int.Parse (args [0]);
+               if (args.Length > 1)
+                       count = int.Parse (args [1]);
+               for (int i = 0; i < loops; ++i) {
+                       work ();
+               }
+       }
+}
+
diff --git a/mono/tests/gchandle-stress.cs b/mono/tests/gchandle-stress.cs
new file mode 100644 (file)
index 0000000..382caf2
--- /dev/null
@@ -0,0 +1,117 @@
+using System;
+using System.Runtime.InteropServices;
+
+// in this test we spend 
+// 30% of the time locking
+// 10 % allocating the handles
+class T {
+
+       static GCHandle[] handle_array;
+
+       static int count = 4 * 400000; /* multiple of handle types */
+       static int loops = 2;
+
+       static void build_array () {
+               int i;
+               handle_array = new GCHandle [count];
+
+               for (i = 0; i < count; ++i) {
+                       GCHandleType t = (GCHandleType) (i & 3);
+                       handle_array [i] = GCHandle.Alloc (i, t);
+               }
+       }
+       static void get_stats (){
+               int i;
+               object o;
+               int has_target = 0;
+               int is_allocated = 0;
+               int normal_reclaimed = 0;
+               for (i = 0; i < count; ++i) {
+                       GCHandleType t = (GCHandleType) (i & 3);
+                       if (handle_array [i].IsAllocated)
+                               is_allocated++;
+                       else
+                               continue;
+                       o = handle_array [i].Target;
+                       if (o != null) {
+                               has_target++;
+                               int val = (int)o;
+                               if (val != i)
+                                       Console.WriteLine ("obj at {0} inconsistent: {1}", i, val);
+                       } else {
+                               if (t == GCHandleType.Normal || t == GCHandleType.Pinned) {
+                                       normal_reclaimed++;
+                               }
+                       }
+               }
+               Console.WriteLine ("allocated: {0}, has target: {1}, normal reclaimed: {2}", is_allocated, has_target, normal_reclaimed);
+       }
+
+       static void free_some (int d) {
+               int i;
+               int freed = 0;
+               for (i = 0; i < count; ++i) {
+                       if ((i % d) == 0) {
+                               if (handle_array [i].IsAllocated) {
+                                       handle_array [i].Free ();
+                                       freed++;
+                               }
+                       }
+               }
+               Console.WriteLine ("freed: {0}", freed);
+       }
+
+       static void alloc_many () {
+               int small_count = count / 2;
+               GCHandle[] more = new GCHandle [small_count];
+               int i;
+               for (i = 0; i < small_count; ++i) {
+                       GCHandleType t = (GCHandleType) (i & 3);
+                       more [i] = GCHandle.Alloc (i, t);
+               }
+               for (i = 0; i < small_count; ++i) {
+                       more [i].Free ();
+               }
+               Console.WriteLine ("alloc many: {0}", small_count);
+       }
+
+       static void Main (string[] args) {
+               if (args.Length > 0)
+                       count = 4 * int.Parse (args [0]);
+               if (args.Length > 1)
+                       loops = int.Parse (args [1]);
+
+               for (int j = 0; j < loops; ++j) {
+                       do_one ();
+               }
+       }
+
+       static void do_one () {
+               Console.WriteLine ("start");
+               build_array ();
+               get_stats ();
+               GC.Collect ();
+               Console.WriteLine ("after collect");
+               get_stats ();
+               free_some (10);
+               Console.WriteLine ("after free(10)");
+               get_stats ();
+               free_some (4);
+               Console.WriteLine ("after free(4)");
+               get_stats ();
+               GC.Collect ();
+               Console.WriteLine ("after collect");
+               get_stats ();
+               for (int i = 0; i < 10; ++i)
+                       alloc_many ();
+               Console.WriteLine ("after alloc_many");
+               get_stats ();
+               free_some (1);
+               Console.WriteLine ("after free all");
+               get_stats ();
+               GC.Collect ();
+               Console.WriteLine ("after collect");
+               get_stats ();
+       }
+}
+
diff --git a/mono/tests/monitor-stress.cs b/mono/tests/monitor-stress.cs
new file mode 100644 (file)
index 0000000..cdda429
--- /dev/null
@@ -0,0 +1,60 @@
+using System;
+using System.Threading;
+
+class T {
+       static int count = 20000;
+       static int loops = 80;
+       static int threads = 10;
+       static object global_obj;
+       static void stress_loop () {
+               object obj = new object ();
+               lock (obj) {
+                       object [] array = new object [count];
+                       for (int i = 0; i < count; ++i) {
+                               array [i] = new object ();
+                       }
+                       for (int i = 0; i < count; ++i) {
+                               lock (array [i]) {
+                                       global_obj = new String ('x', 32);
+                                       if ((i % 12) == 0) {
+                                               array [i] = global_obj;
+                                       }
+                               }
+                       }
+                       // again, after a GC
+                       GC.Collect ();
+                       for (int i = 0; i < count; ++i) {
+                               lock (array [i]) {
+                               }
+                       }
+                       // two times, with feeling
+                       for (int i = 0; i < count; ++i) {
+                               lock (array [i]) {
+                                       for (int j = 0; i < count; ++i) {
+                                               lock (array [j]) {
+                                               }
+                                       }
+                               }
+                       }
+               }
+       }
+
+       static void worker () {
+               for (int i = 0; i < loops; ++i)
+                       stress_loop ();
+       }
+       static void Main (string[] args) {
+               if (args.Length > 0)
+                       loops = int.Parse (args [0]);
+               if (args.Length > 1)
+                       count = int.Parse (args [1]);
+               if (args.Length > 1)
+                       threads = int.Parse (args [2]);
+               for (int i = 0; i < threads; ++i) {
+                       Thread t = new Thread (new ThreadStart (worker));
+                       t.Start ();
+               }
+               /* for good measure */
+               worker ();
+       }
+}
diff --git a/mono/tests/stress-runner.pl b/mono/tests/stress-runner.pl
new file mode 100755 (executable)
index 0000000..4d9e6c2
--- /dev/null
@@ -0,0 +1,219 @@
+#!/usr/bin/perl -w
+
+# mono stress test tool
+# This stress test runner is designed to detect possible
+# leaks, runtime slowdowns and crashes when a task is performed
+# repeatedly.
+# A stress program should be written to repeat for a number of times
+# a specific task: it is run a first time to collect info about memory
+# and cpu usage: this run should last a couple of seconds or so.
+# Then, the same program is run with a number of iterations that is at least
+# 2 orders of magnitude bigger than the first run (3 orders should be used,
+# eventually, to detect smaller leaks).
+# Of course the right time for the test and the ratio depends on the test
+# itself, so it's configurable per-test.
+# The test driver will then check that the second run has used roughly the
+# same amount of memory as the first and a proportionally bigger cpu time.
+# Note: with a conservative GC there may be more false positives than
+# with a precise one, because heap size may grow depending on timing etc.
+# so failing results need to be checked carefully. In some cases a solution
+# is to increase the number of runs in the dry run.
+
+use POSIX ":sys_wait_h";
+use Time::HiRes qw(usleep ualarm gettimeofday tv_interval);
+
+# in milliseconds between checks of resource usage
+my $interval = 50;
+# multiplier to allow some wiggle room
+my $wiggle_ratio = 1.05;
+# if the test computer is too fast or if we want to stress test more,
+# we multiply the test ratio by this number. Use the --times=x option.
+my $extra_strong = 1;
+
+# descriptions of the tests to run
+# for each test:
+#      program is the program to run
+#      args an array ref of argumenst to pass to program
+#      arg-knob is the index of the argument in args that changes the number of iterations
+#      ratio is the multiplier applied to the arg-knob argument
+my %tests = (
+       'domain-stress' => {
+               'program' => 'domain-stress.exe',
+               # threads, domains, allocs, loops
+               'args' => [2, 10, 1000, 1],
+               'arg-knob' => 3, # loops
+               'ratio' => 30,
+       },
+       'gchandle-stress' => {
+               'program' => 'gchandle-stress.exe',
+               # allocs, loops
+               'args' => [80000, 2],
+               'arg-knob' => 1, # loops
+               'ratio' => 20,
+       },
+       'monitor-stress' => {
+               'program' => 'monitor-stress.exe',
+               # loops
+               'args' => [10],
+               'arg-knob' => 0, # loops
+               'ratio' => 20,
+       },
+       'gc-stress' => {
+               'program' => 'gc-stress.exe',
+               # loops
+               'args' => [25],
+               'arg-knob' => 0, # loops
+               'ratio' => 20,
+       },
+       'thread-stress' => {
+               'program' => 'thread-stress.exe',
+               # loops
+               'args' => [20],
+               'arg-knob' => 0, # loops
+               'ratio' => 20,
+       },
+);
+
+# poor man option handling
+while (@ARGV) {
+       my $arg = shift @ARGV;
+       if ($arg =~ /^--times=(\d+)$/) {
+               $extra_strong = $1;
+               next;
+       }
+       if ($arg =~ /^--interval=(\d+)$/) {
+               $interval = $1;
+               next;
+       }
+       unshift @ARGV, $arg;
+       last;
+}
+my $test_rx = shift (@ARGV) || '.';
+# the mono runtime to use and the arguments to pass to it
+my @mono_args = @ARGV;
+my @results = ();
+my %vmmap = qw(VmSize 0 VmLck 1 VmRSS 2 VmData 3 VmStk 4 VmExe 5 VmLib 6);
+my @vmnames = sort {$vmmap{$a} <=> $vmmap{$b}} keys %vmmap;
+# VmRSS depends on the operating system's decisions
+my %vmignore = qw(VmRSS 1);
+my $errorcount = 0;
+my $numtests = 0;
+
+@mono_args = 'mono' unless @mono_args;
+
+apply_options ();
+
+foreach my $test (sort keys %tests) {
+       next unless ($tests{$test}->{'program'} =~ /$test_rx/);
+       $numtests++;
+       run_test ($test, 'dry');
+       run_test ($test, 'stress');
+}
+
+# print all the reports at the end
+foreach my $test (sort keys %tests) {
+       next unless ($tests{$test}->{'program'} =~ /$test_rx/);
+       print_test_report ($test);
+}
+
+print "No tests matched '$test_rx'.\n" unless $numtests;
+
+if ($errorcount) {
+       print "Total issues: $errorcount\n";
+       exit (1);
+} else {
+       exit (0);
+}
+
+sub run_test {
+       my ($name, $mode) = @_;
+       my $test = $tests {$name};
+       my @targs = (@mono_args, $test->{program});
+       my @results = ();
+       my @rargs = @{$test->{"args"}};
+
+       if ($mode ne "dry") {
+               # FIXME: set also a timeout
+               $rargs [$test->{"arg-knob"}] *= $test->{"ratio"};
+       }
+       push @targs, @rargs;
+       print "Running test '$name' in $mode mode\n";
+       my $start_time = [gettimeofday];
+       my $pid = fork ();
+       if ($pid == 0) {
+               exec @targs;
+               die "Cannot exec: $! (@targs)\n";
+       } else {
+               my $kid;
+               do {
+                       $kid = waitpid (-1, WNOHANG);
+                       my $sample = collect_memusage ($pid);
+                       push @results, $sample if (defined ($sample) && @{$sample});
+                       # sleep for a few ms
+                       usleep ($interval * 1000) unless $kid > 0;
+               } until $kid > 0;
+       }
+       my $end_time = [gettimeofday];
+       $test->{"$mode-cputime"} = tv_interval ($start_time, $end_time);
+       $test->{"$mode-memusage"} = [summarize_result (@results)];
+}
+
+sub print_test_report {
+       my ($name) = shift;
+       my $test = $tests {$name};
+       my ($cpu_dry, $cpu_test) = ($test->{'dry-cputime'}, $test->{'stress-cputime'});
+       my @dry_mem = @{$test->{'dry-memusage'}};
+       my @test_mem = @{$test->{'stress-memusage'}};
+       my $ratio = $test->{'ratio'};
+       print "Report for test: $name\n";
+       print "Cpu usage: dry: $cpu_dry, stress: $cpu_test\n";
+       print "Memory usage (KB):\n";
+       print "\t       ",join ("\t", @vmnames), "\n";
+       print "\t   dry: ", join ("\t", @dry_mem), "\n";
+       print "\tstress: ", join ("\t", @test_mem), "\n";
+       if ($cpu_test > ($cpu_dry * $ratio) * $wiggle_ratio) {
+               print "Cpu usage not proportional to ratio $ratio.\n";
+               $errorcount++;
+       }
+       my $i;
+       for ($i = 0; $i < @dry_mem; ++$i) {
+               next if exists $vmignore {$vmnames [$i]};
+               if ($test_mem [$i] > $dry_mem [$i] * $wiggle_ratio) {
+                       print "Memory usage $vmnames[$i] not constant.\n";
+                       $errorcount++;
+               }
+       }
+}
+
+sub collect_memusage {
+       my ($pid) = @_;
+       open (PROC, "</proc/$pid/status") || return undef; # might be dead already
+       my @sample = ();
+       while (<PROC>) {
+               next unless /^(Vm.*?):\s+(\d+)\s+kB/;
+               $sample [$vmmap {$1}] = $2;
+       }
+       close (PROC);
+       return \@sample;
+}
+
+sub summarize_result {
+       my (@data) = @_;
+       my (@result) = (0) x 7;
+       my $i;
+       foreach my $sample (@data) {
+               for ($i = 0; $i < 7; ++$i) {
+                       if ($sample->[$i] > $result [$i]) {
+                               $result [$i] = $sample->[$i];
+                       }
+               }
+       }
+       return @result;
+}
+
+sub apply_options {
+       foreach my $test (values %tests) {
+               $test->{args}->[$test->{'arg-knob'}] *= $extra_strong;
+       }
+}
+
diff --git a/mono/tests/thread-stress.cs b/mono/tests/thread-stress.cs
new file mode 100644 (file)
index 0000000..1b6c7f9
--- /dev/null
@@ -0,0 +1,33 @@
+using System;
+using System.Threading;
+
+class T {
+       static int loops = 20;
+       static int threads = 100;
+
+       static void worker () {
+               /* a writeline happens to involve lots of code */
+               Console.WriteLine ("Thread start " + Thread.CurrentThread.GetHashCode ());
+       }
+
+       static void doit () {
+               Thread[] ta = new Thread [threads];
+               for (int i = 0; i < threads; ++i) {
+                       ta [i] = new Thread (new ThreadStart (worker));
+                       ta [i].Start ();
+               }
+               for (int i = 0; i < threads; ++i) {
+                       ta [i].Join ();
+               }
+       }
+       static void Main (string[] args) {
+               if (args.Length > 0)
+                       loops = int.Parse (args [0]);
+               if (args.Length > 1)
+                       threads = int.Parse (args [1]);
+               for (int i = 0; i < loops; ++i) {
+                       doit ();
+               }
+       }
+}
+