Browse files

Added some stress tests and test driver for them.

svn path=/trunk/mono/; revision=40972
  • Loading branch information...
1 parent fa9d786 commit a4b91cb84528653f8fa72df2c9cd970e6abee527 @illupus illupus committed Feb 21, 2005
View
30 mono/tests/Makefile.am
@@ -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
View
68 mono/tests/domain-stress.cs
@@ -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;
+ }
+}
+
View
26 mono/tests/gc-stress.cs
@@ -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 ();
+ }
+ }
+}
+
View
117 mono/tests/gchandle-stress.cs
@@ -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 ();
+ }
+}
+
View
60 mono/tests/monitor-stress.cs
@@ -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 ();
+ }
+}
View
219 mono/tests/stress-runner.pl
@@ -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;
+ }
+}
+
View
33 mono/tests/thread-stress.cs
@@ -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 ();
+ }
+ }
+}
+

0 comments on commit a4b91cb

Please sign in to comment.