kerneltest/e32test/demandpaging/t_thrash.cpp
branchRCL_3
changeset 26 c734af59ce98
parent 6 0173bcd7697c
child 28 5b5d147c7838
--- a/kerneltest/e32test/demandpaging/t_thrash.cpp	Tue Apr 27 18:02:57 2010 +0300
+++ b/kerneltest/e32test/demandpaging/t_thrash.cpp	Tue May 11 17:28:22 2010 +0300
@@ -27,6 +27,7 @@
 #include <e32msgqueue.h>
 #include <e32atomics.h>
 #include <e32math.h>
+#include <hal.h>
 
 #include "t_dpcmn.h"
 #include "../mmu/mmudetect.h"
@@ -39,11 +40,12 @@
 
 _LIT(KChunkName, "t_thrash chunk");
 
-class TRandom
+class TPRNG 
 	{
 public:
-	TRandom();
-	TUint32 Next();
+	TPRNG();
+	TUint32 IntRand();
+	TReal FloatRand();
 
 private:
 	enum
@@ -54,17 +56,178 @@
 	TUint32 iV;
 	};
 
-TRandom::TRandom()
+TPRNG::TPRNG()
 	{
 	iV = (TUint32)this + RThread().Id() + User::FastCounter() + 23;
 	}
 
-TUint32 TRandom::Next()
+TUint32 TPRNG::IntRand()
 	{
 	iV = KA * iV + KB;
 	return iV;
 	}
 
+TReal TPRNG::FloatRand()
+	{
+	return (TReal)IntRand() / KMaxTUint32;
+	}
+
+class TRandom
+	{
+public:
+	virtual ~TRandom() { }
+	virtual TUint32 Next() = 0;
+	};
+
+ class TUniformRandom : public TRandom
+	{
+public:
+	void SetParams(TUint aMax) { iMax = aMax; }
+	virtual TUint32 Next();
+
+private:
+	TPRNG iRand;
+	TUint iMax;
+	};
+
+TUint32 TUniformRandom::Next()
+	{
+	return iRand.IntRand() % iMax;
+	}
+
+class TNormalRandom : public TRandom
+	{
+public:
+	void SetParams(TInt aMax, TInt aSd);
+	virtual TUint32 Next();
+
+private:
+	TUint32 GetNext();
+
+private:
+	TPRNG iRand;
+	TInt iMax;
+	TInt iExpectation;
+	TInt iSd;
+	TUint32 iCached;
+	};
+
+void TNormalRandom::SetParams(TInt aMax, TInt aSd)
+	{
+	iMax = aMax;
+	iExpectation = aMax / 2;
+	iSd = aSd;
+	iCached = KMaxTUint32;
+	}
+
+TUint32 TNormalRandom::Next()
+	{
+	TUint32 r;
+	do
+		{
+		r = GetNext();
+		}
+	while (r > (TUint)iMax);
+	return r;
+	}
+
+TUint32 TNormalRandom::GetNext()
+	{
+	if (iCached != KMaxTUint32)
+		{
+		TUint32 r = iCached;
+		iCached = KMaxTUint32;
+		return r;
+		}
+	
+	// box-muller transform
+	// from http://www.taygeta.com/random/gaussian.html
+
+	TReal x1, x2, w, ln_w, y1, y2;
+	do
+		{
+		x1 = 2.0 * iRand.FloatRand() - 1.0;
+		x2 = 2.0 * iRand.FloatRand() - 1.0;
+		w = x1 * x1 + x2 * x2;
+		}
+	while ( w >= 1.0 );
+
+	TInt r = Math::Ln(ln_w, w);
+	__ASSERT_ALWAYS(r == KErrNone, User::Invariant());
+	w = (-2.0 * ln_w ) / w;
+	TReal w2;
+	r = Math::Sqrt(w2, w);
+	__ASSERT_ALWAYS(r == KErrNone, User::Invariant());
+	y1 = x1 * w2;
+	y2 = x2 * w2;
+
+	y1 = y1 * iSd + iExpectation;
+	y2 = y2 * iSd + iExpectation;
+
+	iCached = (TUint32)y2;
+
+	return (TUint32)y1;
+	}
+
+static TBool BenchmarksSupported = EFalse;
+static TReal BenchmarkMultiplier;
+
+static TInt InitBenchmarks()
+	{
+	BenchmarksSupported = UserSvr::HalFunction(EHalGroupVM, EVMHalResetPagingBenchmark, (TAny*)EPagingBmReadRomPage, NULL) == KErrNone;
+	if (!BenchmarksSupported)
+		return KErrNone;
+	
+	TInt freq = 0;
+	TInt r = HAL::Get(HAL::EFastCounterFrequency, freq);
+	if (r != KErrNone)
+		return r;
+	BenchmarkMultiplier = 1000000.0 / freq;
+	return KErrNone;
+	}
+
+static void ResetBenchmarks()
+	{
+	if (!BenchmarksSupported)
+		return;	
+	for (TInt i = 0 ; i < EMaxPagingBm ; ++i)
+		{
+		TInt r = UserSvr::HalFunction(EHalGroupVM, EVMHalResetPagingBenchmark, (TAny*)i, NULL);
+		if (r != KErrNone)
+			test.Printf(_L("Error resetting benchmark %d\n"), i);
+		test_KErrNone(r);
+		}
+	}
+
+static TInt GetBenchmark(TPagingBenchmark aBenchmark, TInt& aCountOut, TInt& aTotalTimeInMicrosOut)
+	{
+	
+	SPagingBenchmarkInfo info;
+	TInt r = UserSvr::HalFunction(EHalGroupVM, EVMHalGetPagingBenchmark, (TAny*)aBenchmark, &info);
+	if (r!=KErrNone)
+		return r;
+	
+	aCountOut = info.iCount;
+	aTotalTimeInMicrosOut = (TInt)(info.iTotalTime * BenchmarkMultiplier);
+	return KErrNone;
+	}
+
+static TInt GetAllBenchmarks(TInt aTestLengthInSeconds, TInt aCountOut[EMaxPagingBm], TInt aTimeOut[EMaxPagingBm])
+	{
+	for (TInt i = 0 ; i < EMaxPagingBm ; ++i)
+		{
+		TInt count = 0;
+		TInt timeInMicros = 0;
+		TInt r = GetBenchmark((TPagingBenchmark)i, count, timeInMicros);
+		if (r != KErrNone)
+			return r;
+		
+		aCountOut[i] = count / aTestLengthInSeconds;
+		aTimeOut[i] = timeInMicros / aTestLengthInSeconds;
+		}
+	return KErrNone;	
+	}
+
 void CreatePagedChunk(TInt aSizeInPages)
 	{
 	test_Equal(0,gChunk.Handle());
@@ -84,11 +247,70 @@
 	return (TUint32*)(gChunk.Base() + (gPageSize * aPage));
 	}
 
+TInt EnsureSystemIdleThread(TAny*)
+	{
+	RThread::Rendezvous(KErrNone);
+	for (;;)
+		{
+		// Spin
+		}
+	}
+
+void EnsureSystemIdle()
+	{
+	const TInt KMaxWait = 60 * 1000000;
+	const TInt KSampleTime = 1 * 1000000;
+	const TInt KWaitTime = 5 * 1000000;
+	
+	test.Printf(_L("Waiting for system to become idle\n"));
+	TInt totalTime = 0;
+	TBool idle;
+	do
+		{	
+		RThread thread;
+		test_KErrNone(thread.Create(_L("EnsureSystemIdleThread"), EnsureSystemIdleThread, 1024, NULL, NULL));		
+		thread.SetPriority(EPriorityLess);
+		thread.Resume();
+
+		TRequestStatus status;
+		thread.Rendezvous(status);
+		User::WaitForRequest(status);
+		test_KErrNone(status.Int());
+
+		User::After(KSampleTime);
+		thread.Suspend();
+
+		TTimeIntervalMicroSeconds time;
+		test_KErrNone(thread.GetCpuTime(time));
+		TReal error = (100.0 * Abs(time.Int64() - KSampleTime)) / KSampleTime;
+		test.Printf(_L("    time == %ld, error == %f%%\n"), time.Int64(), error);
+
+		idle = error < 2.0;		
+		
+		thread.Kill(KErrNone);
+		thread.Logon(status);
+		User::WaitForRequest(status);
+		test_KErrNone(status.Int());
+		CLOSE_AND_WAIT(thread);
+		
+		if (!idle)
+			User::After(KWaitTime);		// Allow system to finish whatever it's doing
+
+		totalTime += KSampleTime + KWaitTime;
+		test(totalTime < KMaxWait);
+		}
+	while(!idle);
+	}
+
 enum TWorkload
 	{
 	EWorkloadSequential,
-	EWorkloadRandom,
-	EWorkloadShuffle
+	EWorkloadUniformRandom,
+	EWorkloadNormalRandom1,
+	EWorkloadNormalRandom2,
+	EWorkloadShuffle,
+
+	EMaxWorkloads
 	};
 
 struct SThrashTestArgs
@@ -105,55 +327,86 @@
 	{
 	SThrashTestArgs* args = (SThrashTestArgs*)aArg;
 
-	TRandom random;
+	TPRNG random;
+	TUniformRandom uniformRand;
+	TNormalRandom normalRand;
+
 	TInt startPage = args->iThreadGroup * args->iGroupSize;
 	TInt* ptr = (TInt*)(args->iBasePtr + startPage * gPageSize);
+
+	
 	switch (args->iWorkload)
 		{
 		case EWorkloadSequential:
 			while (gRunThrashTest)
 				{
-				TInt size = (args->iPageCount * gPageSize) / sizeof(TInt);
-				for (TInt i = 0 ; i < size && gRunThrashTest ; ++i)
+				for (TUint i = 0 ;
+					 gRunThrashTest && i < (args->iPageCount * gPageSize) / sizeof(TInt)  ;
+					 ++i)
 					{
-					ptr[i] = random.Next();
+					ptr[i] = 1;
 					__e32_atomic_add_ord64(&args->iAccesses, 1);
 					}
 				}
 			break;
 				
-		case EWorkloadRandom:
+		case EWorkloadUniformRandom:
+		case EWorkloadNormalRandom1:
+		case EWorkloadNormalRandom2:
 			{
 			TInt acc = 0;
+			TInt oldSize = -1;
+			TUint32 writeMask = 0;
+			switch (args->iWorkload)
+				{
+				case EWorkloadUniformRandom:
+				case EWorkloadNormalRandom1:
+					writeMask = 0x80000000; break;
+				case EWorkloadNormalRandom2:
+					writeMask = 0xc0000000; break;
+				default: test(EFalse); break;
+				}
 			while (gRunThrashTest)
 				{
-				TInt size = (args->iPageCount * gPageSize) / sizeof(TInt);
-				for (TInt i = 0 ; i < size && gRunThrashTest ; ++i)
+				TInt size = args->iPageCount;
+				if (size != oldSize)
 					{
-					TUint32 rand = random.Next();
-					TInt action = rand >> 31;
-					TInt r = rand % size;
-					if (action == 0)
-						acc += ptr[r];
-					else
-						ptr[r] = acc;
-					__e32_atomic_add_ord64(&args->iAccesses, 1);
+					switch (args->iWorkload)
+						{
+						case EWorkloadUniformRandom:
+							uniformRand.SetParams(size); break;
+						case EWorkloadNormalRandom1:
+						case EWorkloadNormalRandom2:
+							normalRand.SetParams(size, size / 8); break;
+						default: test(EFalse); break;
+						}
+					oldSize = size;
 					}
+				
+				TInt page = args->iWorkload == EWorkloadUniformRandom ?
+					uniformRand.Next() : normalRand.Next();
+				TInt index = page * (gPageSize / sizeof(TInt));
+				TBool write = (random.IntRand() & writeMask) == 0;
+				if (write)
+					ptr[index] = acc;
+				else
+					acc += ptr[index];
+				__e32_atomic_add_ord64(&args->iAccesses, 1);
 				}
 			}
 			break;
 			
 		case EWorkloadShuffle:
 			{
-			TInt i;
+			TInt i = 0;
 			while (gRunThrashTest)
 				{
 				TInt size = (args->iPageCount * gPageSize) / sizeof(TInt);
-				for (i = 0 ; gRunThrashTest && i < (size - 1) ; ++i)
-					{
-					Mem::Swap(&ptr[i], &ptr[i + random.Next() % (size - i - 1) + 1], sizeof(TInt));
-					__e32_atomic_add_ord64(&args->iAccesses, 2);
-					}
+				Mem::Swap(&ptr[i], &ptr[i + random.IntRand() % (size - i - 1) + 1], sizeof(TInt));
+				__e32_atomic_add_ord64(&args->iAccesses, 2);
+				++i;
+				if (i >= size - 1)
+					i = 0;
 				}
 			}
 			break;
@@ -172,17 +425,32 @@
 	SThrashTestArgs iArgs;
 	};
 
-void ThrashTest(TInt aThreads,			// number of threads to run
+void ThrashTest(const TDesC& aTestName,	// name and description
+				TInt aThreads,			// number of threads to run
 				TBool aSharedData,		// whether all threads share the same data
 				TWorkload aWorkload,
 				TInt aBeginPages,		// number of pages to start with for last/all threads
 				TInt aEndPages,			// number of pages to end with for last/all threads
 				TInt aOtherPages)		// num of pages for other threads, or zero to use same value for all
 	{
-	RDebug::Printf("\nPages Accesses     ThL");
+	const TInt KTestLengthInSeconds = 2;
+	
+	test.Next(_L("Thrash test"));
+	
+	DPTest::FlushCache();
+	EnsureSystemIdle();
 
-	DPTest::FlushCache();
-	User::After(1000000);
+	TInt i;
+	test.Printf(_L("Table: %S\n"), &aTestName);
+	test.Printf(_L("totalPages, totalAccesses, thrashLevel"));
+	if (BenchmarksSupported)
+		test.Printf(_L(", rejuveCount, rejuveTime, codePageInCount, codePageInTime, initCount, initTime, readCount, readTime, writePages, writeCount, writeTime"));
+	if (aThreads > 1)
+		{
+		for (TInt i = 0 ; i < aThreads ; ++i)
+			test.Printf(_L(", Thread%dPages, Thread%dAccesses"), i, i);
+		}
+	test.Printf(_L("\n"));
 
 	TInt pagesNeeded;
 	TInt maxPages = Max(aBeginPages, aEndPages);
@@ -208,11 +476,10 @@
 	test_NotNull(threads);
 	
 	gRunThrashTest = ETrue;
-	TInt pageCount = aBeginPages;
 	const TInt maxSteps = 30;
 	TInt step = aEndPages >= aBeginPages ? Max((aEndPages - aBeginPages) / maxSteps, 1) : Min((aEndPages - aBeginPages) / maxSteps, -1);
+	TInt pageCount = aBeginPages - 5 * step; // first run ignored
 	
-	TInt i;
 	for (i = 0 ; i < aThreads ; ++i)
 		{
 		SThrashThreadData& thread = threads[i];
@@ -242,8 +509,10 @@
 		
 		for (i = 0 ; i < aThreads ; ++i)
 			__e32_atomic_store_ord64(&threads[i].iArgs.iAccesses, 0);
+		ResetBenchmarks();
 		
-		User::After(2000000);		
+		User::After(KTestLengthInSeconds * 1000 * 1000);
+
 		TInt thrashLevel = UserSvr::HalFunction(EHalGroupVM, EVMHalGetThrashLevel, 0, 0);
 		test(thrashLevel >= 0 && thrashLevel <= 255);
 		
@@ -257,20 +526,50 @@
 			else
 				totalPages += threads[i].iArgs.iPageCount;
 			}
+		TInt accessesPerSecond = (TInt)(totalAccesses / KTestLengthInSeconds);
 
-		test.Printf(_L("%5d %12ld %3d"), totalPages, totalAccesses, thrashLevel);
-		for (i = 0 ; i < aThreads ; ++i)
+		TBool warmingUp = (step > 0) ? pageCount < aBeginPages : pageCount > aBeginPages;
+		if (!warmingUp)
 			{
-			test.Printf(_L(" %5d %12ld"),
-						threads[i].iArgs.iPageCount,
-						__e32_atomic_load_acq64(&threads[i].iArgs.iAccesses));
-			test_Equal(KRequestPending, threads[i].iStatus.Int());
+			test.Printf(_L("%10d, %13d, %11.2f"), totalPages, accessesPerSecond, (TReal)thrashLevel / 255);
+		
+			if (BenchmarksSupported)
+				{
+				TInt benchmarkCount[EMaxPagingBm];
+				TInt benchmarkTime[EMaxPagingBm];
+				test_KErrNone(GetAllBenchmarks(KTestLengthInSeconds, benchmarkCount, benchmarkTime));
+
+				TInt otherPageInCount = benchmarkCount[EPagingBmReadRomPage] + benchmarkCount[EPagingBmReadCodePage];
+				TInt otherPageInTime = benchmarkTime[EPagingBmReadRomPage] + benchmarkTime[EPagingBmReadCodePage];
+		
+				TInt initCount = benchmarkCount[EPagingBmReadDataPage] - benchmarkCount[EPagingBmReadDataMedia];
+				TInt initTime = benchmarkTime[EPagingBmReadDataPage] - benchmarkTime[EPagingBmReadDataMedia];
+
+				test.Printf(_L(", %11d, %10d, %15d, %14d, %9d, %8d, %9d, %8d, %10d, %10d, %9d"),
+							benchmarkCount[EPagingBmRejuvenate], benchmarkTime[EPagingBmRejuvenate],
+							otherPageInCount, otherPageInTime,
+							initCount, initTime,
+							benchmarkCount[EPagingBmReadDataMedia], benchmarkTime[EPagingBmReadDataMedia],
+							benchmarkCount[EPagingBmWriteDataPage], 
+							benchmarkCount[EPagingBmWriteDataMedia], benchmarkTime[EPagingBmWriteDataMedia]);
+				}
+		
+			if (aThreads > 1)
+				{
+				for (i = 0 ; i < aThreads ; ++i)
+					{
+					test.Printf(_L(", %12d, %15ld"),
+								threads[i].iArgs.iPageCount,
+								__e32_atomic_load_acq64(&threads[i].iArgs.iAccesses));
+					test_Equal(KRequestPending, threads[i].iStatus.Int());
+					}
+				}
+			test.Printf(_L("\n"));
 			}
-		test.Printf(_L("\n"));
 
+		pageCount += step;
 		if (aEndPages >= aBeginPages ? pageCount >= aEndPages : pageCount < aEndPages)
 			break;
-		pageCount += step;
 		}
 	
 	gRunThrashTest = EFalse;
@@ -285,7 +584,7 @@
 		}
 
 	gChunk.Close();	
-	RDebug::Printf("\n");
+	test.Printf(_L("\n"));
 	}
 
 void TestThrashing()
@@ -298,47 +597,96 @@
 	TInt maxPages4 = (5 * gMaxCacheSize) / 16;
 
 	// Single thread increasing in size
-	test.Next(_L("Thrash test: single thread, sequential workload"));
-	ThrashTest(1, ETrue, EWorkloadSequential, minPages, maxPages, 0);
+	ThrashTest(_L("single thread, sequential workload"),
+			   1, ETrue, EWorkloadSequential, minPages, maxPages, 0);
 	
-	test.Next(_L("Thrash test: single thread, random workload"));
-	ThrashTest(1, ETrue, EWorkloadRandom, minPages, maxPages, 0);
+	ThrashTest(_L("single thread, random workload"),
+			   1, ETrue, EWorkloadUniformRandom, minPages, maxPages, 0);
 	
-	test.Next(_L("Thrash test: single thread, shuffle workload"));
-	ThrashTest(1, ETrue, EWorkloadShuffle, minPages, maxPages, 0);
+	ThrashTest(_L("single thread, shuffle workload"),
+			   1, ETrue, EWorkloadShuffle, minPages, maxPages, 0);
 
 	// Multiple threads with shared data, one thread incresing in size
-	test.Next(_L("Thrash test: two threads with shared data, one thread increasing, random workload"));
-	ThrashTest(2, ETrue, EWorkloadRandom, minPages, maxPages, minPages);
+	ThrashTest(_L("two threads with shared data, one thread increasing, random workload"),
+			   2, ETrue, EWorkloadUniformRandom, minPages, maxPages, minPages);
 	
-	test.Next(_L("Thrash test: four threads with shared data, one thread increasing, random workload"));
-	ThrashTest(4, ETrue, EWorkloadRandom, minPages, maxPages, minPages);
+	ThrashTest(_L("four threads with shared data, one thread increasing, random workload"),
+			   4, ETrue, EWorkloadUniformRandom, minPages, maxPages, minPages);
 
 	// Multiple threads with shared data, all threads incresing in size
-	test.Next(_L("Thrash test: two threads with shared data, all threads increasing, random workload"));
-	ThrashTest(2, ETrue, EWorkloadRandom, minPages, maxPages, 0);
+	ThrashTest(_L("two threads with shared data, all threads increasing, random workload"),
+			   2, ETrue, EWorkloadUniformRandom, minPages, maxPages, 0);
 	
-	test.Next(_L("Thrash test: four threads with shared data, all threads increasing, random workload"));
-	ThrashTest(4, ETrue, EWorkloadRandom, minPages, maxPages, 0);
+	ThrashTest(_L("four threads with shared data, all threads increasing, random workload"),
+			   4, ETrue, EWorkloadUniformRandom, minPages, maxPages, 0);
 	
 	// Multiple threads with independent data, one thread incresing in size
-	test.Next(_L("Thrash test: two threads with independent data, one thread increasing, random workload"));
-	ThrashTest(2, EFalse, EWorkloadRandom, minPages2, maxPages2, gMaxCacheSize / 2);
+	ThrashTest(_L("two threads with independent data, one thread increasing, random workload"),
+			   2, EFalse, EWorkloadUniformRandom, minPages2, maxPages2, gMaxCacheSize / 2);
 	
-	test.Next(_L("Thrash test: four threads with independent data, one thread increasing, random workload"));
-	ThrashTest(4, EFalse, EWorkloadRandom, minPages4, maxPages4, gMaxCacheSize / 4);
+	ThrashTest(_L("four threads with independent data, one thread increasing, random workload"),
+			   4, EFalse, EWorkloadUniformRandom, minPages4, maxPages4, gMaxCacheSize / 4);
 	
 	// Multiple threads with independant data, all threads incresing in size
-	test.Next(_L("Thrash test: two threads with independent data, all threads increasing, random workload"));
-	ThrashTest(2, EFalse, EWorkloadRandom, minPages2, maxPages2, 0);
+	ThrashTest(_L("two threads with independent data, all threads increasing, random workload"),
+			   2, EFalse, EWorkloadUniformRandom, minPages2, maxPages2, 0);
 
-	test.Next(_L("Thrash test: four threads with independent data, all threads increasing, random workload"));
-	ThrashTest(4, EFalse, EWorkloadRandom, minPages4, maxPages4, 0);
+	ThrashTest(_L("four threads with independent data, all threads increasing, random workload"),
+			   4, EFalse, EWorkloadUniformRandom, minPages4, maxPages4, 0);
 
 	// Attempt to create thrash state where there is sufficient cache
-	test.Next(_L("Thrash test: two threads with independent data, one threads decreasing, random workload"));
 	TInt halfCacheSize = gMaxCacheSize / 2;
-	ThrashTest(2, EFalse, EWorkloadRandom, halfCacheSize + 10, halfCacheSize - 30, halfCacheSize);
+	ThrashTest(_L("two threads with independent data, one threads decreasing, random workload"),
+			   2, EFalse, EWorkloadUniformRandom, halfCacheSize + 10, halfCacheSize - 30, halfCacheSize);
+	}
+
+void TestDistribution(TRandom& aRandom, TInt aSamples)
+	{
+	TUint32* data = new TUint32[aSamples];
+	test_NotNull(data);
+
+	TInt i;
+	TReal mean = 0.0;
+	for (i = 0 ; i < aSamples ; ++i)
+		{
+		data[i] = aRandom.Next();
+		mean += (TReal)data[i] / aSamples;
+		}
+
+	TReal sum2 = 0.0;
+	for (i = 0 ; i < aSamples ; ++i)
+		{
+		TReal d = (TReal)data[i] - mean;
+		sum2 += d * d;
+		}
+	TReal variance = sum2 / (aSamples - 1);
+
+	test.Printf(_L("  mean == %f\n"), mean);
+	test.Printf(_L("  variance == %f\n"), variance);
+
+	delete [] data;
+	}
+
+void BenchmarkReplacement()
+	{
+ 	test.Next(_L("Test uniform distribution"));
+	TUniformRandom rand1;
+	rand1.SetParams(100);
+	TestDistribution(rand1, 10000);
+	
+ 	test.Next(_L("Test normal distribution"));	
+	TNormalRandom rand2;
+	rand2.SetParams(100, 25);
+	TestDistribution(rand2, 10000);
+
+	ThrashTest(_L("Thrash test: single thread, normal random workload 1"),
+			   1, ETrue, EWorkloadNormalRandom1, (2 * gMaxCacheSize) / 3, 2 * gMaxCacheSize, 0);
+	
+	ThrashTest(_L("Thrash test: single thread, normal random workload 2"),
+			   1, ETrue, EWorkloadNormalRandom2, (2 * gMaxCacheSize) / 3, 2 * gMaxCacheSize, 0);
+
+	ThrashTest(_L("Thrash test: single thread, uniform random workload"),
+			   1, ETrue, EWorkloadUniformRandom, (2 * gMinCacheSize) / 3, (3 * gMaxCacheSize) / 2, 0);
 	}
 
 void TestThrashHal()
@@ -369,27 +717,44 @@
 	test_Equal(KRequestPending, status.Int());
 	
 	// stress system and check thrash level and notification
-	ThrashTest(1, ETrue, EWorkloadRandom, gMaxCacheSize * 2, gMaxCacheSize * 2 + 5, 0);
+	ThrashTest(_L("stress system"),
+			   1, ETrue, EWorkloadUniformRandom, gMaxCacheSize * 2, gMaxCacheSize * 2 + 5, 0);
 	r = UserSvr::HalFunction(EHalGroupVM, EVMHalGetThrashLevel, 0, 0);
 	test(r >= 0 && r <= 255);
 	test.Printf(_L("Thrash level == %d\n"), r);
 	test(r > 200);  // should indicate thrashing
-	test_Equal(EChangesThrashLevel, status.Int());
-	User::WaitForAnyRequest();
 
-	// wait for system to calm down and check notification again
-	test_KErrNone(notifier.Logon(status));
-	User::WaitForAnyRequest();
-	test_Equal(EChangesThreadDeath, status.Int());
-
-	test_KErrNone(notifier.Logon(status));
+	TBool gotThrashNotification = EFalse;
+	
+	// wait for EChangesThrashLevel notification
+	while(status.Int() != KRequestPending)
+		{
+		gotThrashNotification = (status.Int() & EChangesThrashLevel) != 0;
+		User::WaitForAnyRequest();
+		test_KErrNone(notifier.Logon(status));
+		User::After(1);		
+		}
+	test(gotThrashNotification);
+	
 	User::After(2000000);
 	r = UserSvr::HalFunction(EHalGroupVM, EVMHalGetThrashLevel, 0, 0);
 	test(r >= 0 && r <= 255);
 	test.Printf(_L("Thrash level == %d\n"), r);
 	test(r <= 10);  // should indicate lightly loaded system
-	test_Equal(EChangesThrashLevel, status.Int());	
+
+	// wait for EChangesThrashLevel notification
+	gotThrashNotification = EFalse;
+	while(status.Int() != KRequestPending)
+		{
+		gotThrashNotification = (status.Int() & EChangesThrashLevel) != 0;
+		User::WaitForAnyRequest();
+		test_KErrNone(notifier.Logon(status));
+		User::After(1);		
+		}
+	test(gotThrashNotification);
+	test_KErrNone(notifier.LogonCancel());
 	User::WaitForAnyRequest();
+	notifier.Close();
 	}
 
 void TestThrashHalNotSupported()
@@ -398,45 +763,105 @@
 	test_Equal(KErrNotSupported, UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, 0, 0));
 	}
 
+_LIT(KUsageMessage, "usage: t_thrash [ test ] [ thrashing ] [ benchmarks ]\n");
+
+enum TTestAction
+	{
+	EActionTest       = 1 << 0,
+	EActionThrashing  = 1 << 1,
+	EActionBenchmarks = 1 << 2
+	};
+
+void BadUsage()
+	{
+	test.Printf(KUsageMessage);
+	test(EFalse);
+	}
+
+TInt ParseCommandLine()
+	{
+	const TInt KMaxLineLength = 64;
+	
+	if (User::CommandLineLength() > KMaxLineLength)
+		BadUsage();
+	TBuf<KMaxLineLength> buffer;
+	User::CommandLine(buffer);
+
+	if (buffer == KNullDesC)
+		return EActionTest;
+	
+	TLex lex(buffer);
+	TInt result = 0;
+	while (!lex.Eos())
+		{
+		TPtrC word = lex.NextToken();
+		if (word == _L("test"))
+			result |= EActionTest;
+		else if (word == _L("thrashing"))
+			result |= EActionThrashing;
+		else if (word == _L("benchmarks"))
+			result |= EActionBenchmarks;
+		else
+			{
+			test.Printf(_L("bad token '%S'\n"), &word);
+			BadUsage();
+			}
+		}
+	
+	return result;
+	}
+
 TInt E32Main()
 	{
 	test.Title();
 	test.Start(_L("Test thrashing monitor"));
+	
+	test_KErrNone(InitBenchmarks());
 
+	TInt actions = ParseCommandLine();
+	
 	test_KErrNone(GetGlobalPolicies());
 
 	TUint cacheOriginalMin = 0;
 	TUint cacheOriginalMax = 0;
-	TUint cacheCurrentSize = 0;
 
 	if (gDataPagingSupported)
 		{
-		test.Next(_L("Thrash test: change maximum cache size to minimal"));
-		//store original values
+		test.Next(_L("Thrash test: change cache size to maximum 2Mb"));
+		TUint cacheCurrentSize = 0;
 		DPTest::CacheSize(cacheOriginalMin, cacheOriginalMax, cacheCurrentSize);
-		gMaxCacheSize = 256;
-		gMinCacheSize = 64;
+		gMinCacheSize = 512;
+		gMaxCacheSize = 520;
 		test_KErrNone(DPTest::SetCacheSize(gMinCacheSize * gPageSize, gMaxCacheSize * gPageSize));
 		}
+	
+	if (actions & EActionTest)
+		{
+		TBool flexibleMemoryModel = (MemModelAttributes() & EMemModelTypeMask) == EMemModelTypeFlexible;
+		if (flexibleMemoryModel)
+			TestThrashHal();
+		else
+			TestThrashHalNotSupported();
+		}
 
-	TBool flexibleMemoryModel = (MemModelAttributes() & EMemModelTypeMask) == EMemModelTypeFlexible;
-	if (flexibleMemoryModel)
-		TestThrashHal();
-	else
-		TestThrashHalNotSupported();
-	
-	if (gDataPagingSupported && User::CommandLineLength() > 0)
-		{		
+	if (actions & EActionThrashing)
+		{	
 		test.Next(_L("Extended thrashing tests"));
 		TestThrashing();
 		}
+	
+	if (actions & EActionBenchmarks)
+		{	
+		test.Next(_L("Benchmarking page replacement"));
+		BenchmarkReplacement();
+		}
+
 	if (gDataPagingSupported)
 		{
-		//Reset the cache size to normal
 		test.Next(_L("Thrash test: Reset cache size to normal"));
 		test_KErrNone(DPTest::SetCacheSize(cacheOriginalMin, cacheOriginalMax));
 		}
-
+	
 	test.End();
 	return 0;
 	}