kerneltest/e32test/demandpaging/t_thrash.cpp
changeset 0 a41df078684a
child 6 0173bcd7697c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/kerneltest/e32test/demandpaging/t_thrash.cpp	Mon Oct 19 15:55:17 2009 +0100
@@ -0,0 +1,422 @@
+// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the License "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// e32test\demandpaging\t_thrash.cpp
+// todo: test combinations of rom / code / data paging
+//
+
+#define __E32TEST_EXTENSION__
+#include <e32test.h>
+#include <dptest.h>
+#include <e32hal.h>
+#include <u32hal.h>
+#include <u32exec.h>
+#include <e32svr.h>
+#include <e32panic.h>
+#include "u32std.h"
+#include <e32msgqueue.h>
+#include <e32atomics.h>
+#include <e32math.h>
+
+#include "t_dpcmn.h"
+#include "../mmu/mmudetect.h"
+#include "../mmu/d_memorytest.h"
+#include "../mmu/t_codepaging_dll.h"
+
+RTest test(_L("T_THRASH"));
+
+volatile TBool gRunThrashTest = EFalse;
+
+_LIT(KChunkName, "t_thrash chunk");
+
+class TRandom
+	{
+public:
+	TRandom();
+	TUint32 Next();
+
+private:
+	enum
+		{
+		KA = 1664525,
+		KB = 1013904223
+		};
+	TUint32 iV;
+	};
+
+TRandom::TRandom()
+	{
+	iV = (TUint32)this + RThread().Id() + User::FastCounter() + 23;
+	}
+
+TUint32 TRandom::Next()
+	{
+	iV = KA * iV + KB;
+	return iV;
+	}
+
+void CreatePagedChunk(TInt aSizeInPages)
+	{
+	test_Equal(0,gChunk.Handle());
+	
+	TChunkCreateInfo createInfo;
+	TInt size = aSizeInPages * gPageSize;
+	createInfo.SetNormal(size, size);
+	createInfo.SetPaging(TChunkCreateInfo::EPaged);
+	createInfo.SetOwner(EOwnerProcess);
+	createInfo.SetGlobal(KChunkName);
+	test_KErrNone(gChunk.Create(createInfo));
+	test(gChunk.IsPaged()); // this is only ever called if data paging is supported
+	}
+
+TUint32* PageBasePtr(TInt aPage)
+	{
+	return (TUint32*)(gChunk.Base() + (gPageSize * aPage));
+	}
+
+enum TWorkload
+	{
+	EWorkloadSequential,
+	EWorkloadRandom,
+	EWorkloadShuffle
+	};
+
+struct SThrashTestArgs
+	{
+	TInt iThreadGroup;
+	TInt iGroupSize;
+	TWorkload iWorkload;
+	TUint8* iBasePtr;
+	volatile TInt iPageCount;
+	volatile TInt64 iAccesses;
+	};
+
+TInt ThrashTestFunc(TAny* aArg)
+	{
+	SThrashTestArgs* args = (SThrashTestArgs*)aArg;
+
+	TRandom random;
+	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)
+					{
+					ptr[i] = random.Next();
+					__e32_atomic_add_ord64(&args->iAccesses, 1);
+					}
+				}
+			break;
+				
+		case EWorkloadRandom:
+			{
+			TInt acc = 0;
+			while (gRunThrashTest)
+				{
+				TInt size = (args->iPageCount * gPageSize) / sizeof(TInt);
+				for (TInt i = 0 ; i < size && gRunThrashTest ; ++i)
+					{
+					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);
+					}
+				}
+			}
+			break;
+			
+		case EWorkloadShuffle:
+			{
+			TInt i;
+			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);
+					}
+				}
+			}
+			break;
+
+		default:
+			test(EFalse);
+		}
+
+	return KErrNone;;
+	}
+
+struct SThrashThreadData
+	{
+	RThread iThread;
+	TRequestStatus iStatus;
+	SThrashTestArgs iArgs;
+	};
+
+void ThrashTest(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");
+
+	DPTest::FlushCache();
+	User::After(1000000);
+
+	TInt pagesNeeded;
+	TInt maxPages = Max(aBeginPages, aEndPages);
+	TInt groupSize = 0;
+	if (aSharedData)
+		pagesNeeded = Max(maxPages, aOtherPages);
+	else
+		{
+		if (aOtherPages)
+			{
+			groupSize = aOtherPages;
+			pagesNeeded = (aThreads - 1) * aOtherPages + maxPages;
+			}
+		else
+			{
+			groupSize = maxPages;
+			pagesNeeded = aThreads * maxPages;
+			}
+		}
+	CreatePagedChunk(pagesNeeded);
+	
+	SThrashThreadData* threads = new SThrashThreadData[aThreads];
+	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 i;
+	for (i = 0 ; i < aThreads ; ++i)
+		{
+		SThrashThreadData& thread = threads[i];
+		thread.iArgs.iThreadGroup = aSharedData ? 0 : i;
+		thread.iArgs.iGroupSize = groupSize;
+		thread.iArgs.iWorkload = aWorkload;
+		thread.iArgs.iBasePtr = gChunk.Base();
+		if (aOtherPages)
+			thread.iArgs.iPageCount = (i == aThreads - 1) ? pageCount : aOtherPages;
+		else
+			thread.iArgs.iPageCount = pageCount;
+		test_KErrNone(thread.iThread.Create(KNullDesC, ThrashTestFunc, gPageSize, NULL, &thread.iArgs));
+		thread.iThread.Logon(thread.iStatus);
+		thread.iThread.SetPriority(EPriorityLess);
+		threads[i].iThread.Resume();
+		}
+
+	for (;;)
+		{
+		if (aOtherPages)
+			threads[aThreads - 1].iArgs.iPageCount = pageCount;
+		else
+			{
+			for (i = 0 ; i < aThreads ; ++i)
+				threads[i].iArgs.iPageCount = pageCount;
+			}
+		
+		for (i = 0 ; i < aThreads ; ++i)
+			__e32_atomic_store_ord64(&threads[i].iArgs.iAccesses, 0);
+		
+		User::After(2000000);		
+		TInt thrashLevel = UserSvr::HalFunction(EHalGroupVM, EVMHalGetThrashLevel, 0, 0);
+		test(thrashLevel >= 0 && thrashLevel <= 255);
+		
+		TInt64 totalAccesses = 0;
+		TInt totalPages = 0;
+		for (i = 0 ; i < aThreads ; ++i)
+			{
+			totalAccesses += __e32_atomic_load_acq64(&threads[i].iArgs.iAccesses);
+			if (aSharedData)
+				totalPages = Max(totalPages, threads[i].iArgs.iPageCount);
+			else
+				totalPages += threads[i].iArgs.iPageCount;
+			}
+
+		test.Printf(_L("%5d %12ld %3d"), totalPages, totalAccesses, thrashLevel);
+		for (i = 0 ; i < aThreads ; ++i)
+			{
+			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("\n"));
+
+		if (aEndPages >= aBeginPages ? pageCount >= aEndPages : pageCount < aEndPages)
+			break;
+		pageCount += step;
+		}
+	
+	gRunThrashTest = EFalse;
+	
+	for (i = 0 ; i < aThreads ; ++i)
+		{
+		SThrashThreadData& thread = threads[i];
+		User::WaitForRequest(thread.iStatus);
+		test_Equal(EExitKill, thread.iThread.ExitType());
+		test_KErrNone(thread.iStatus.Int());
+		thread.iThread.Close();
+		}
+
+	gChunk.Close();	
+	RDebug::Printf("\n");
+	}
+
+void TestThrashing()
+	{
+	TInt minPages = (3 * gMaxCacheSize) / 4 - 4;
+	TInt maxPages = (5 * gMaxCacheSize) / 4;
+	TInt minPages2 = (3 * gMaxCacheSize) / 8 - 4;
+	TInt maxPages2 = (5 * gMaxCacheSize) / 8;
+	TInt minPages4 = (3 * gMaxCacheSize) / 16 - 4;
+	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);
+	
+	test.Next(_L("Thrash test: single thread, random workload"));
+	ThrashTest(1, ETrue, EWorkloadRandom, minPages, maxPages, 0);
+	
+	test.Next(_L("Thrash test: single thread, shuffle workload"));
+	ThrashTest(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);
+	
+	test.Next(_L("Thrash test: four threads with shared data, one thread increasing, random workload"));
+	ThrashTest(4, ETrue, EWorkloadRandom, 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);
+	
+	test.Next(_L("Thrash test: four threads with shared data, all threads increasing, random workload"));
+	ThrashTest(4, ETrue, EWorkloadRandom, 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);
+	
+	test.Next(_L("Thrash test: four threads with independent data, one thread increasing, random workload"));
+	ThrashTest(4, EFalse, EWorkloadRandom, 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);
+
+	test.Next(_L("Thrash test: four threads with independent data, all threads increasing, random workload"));
+	ThrashTest(4, EFalse, EWorkloadRandom, 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);
+	}
+
+void TestThrashHal()
+	{			 
+	test.Next(_L("Test EVMHalSetThrashThresholds"));
+	test_Equal(KErrArgument, UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, (TAny*)256, 0));
+	test_Equal(KErrArgument, UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, (TAny*)0, (TAny*)1));
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, 0, 0));
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, (TAny*)255, 0));
+	test_KErrNone(UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, (TAny*)200, (TAny*)150));
+
+	test.Next(_L("Test EVMHalGetThrashLevel"));
+	User::After(2000000);
+	TInt 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
+
+	if (!gDataPagingSupported)
+		return;  // rest of this test relies on data paging
+
+	// set up thrashing notification
+	RChangeNotifier notifier;
+	test_KErrNone(notifier.Create());
+	TRequestStatus status;
+	test_KErrNone(notifier.Logon(status));
+	test_KErrNone(notifier.Logon(status));  // first logon completes immediately
+	test_Equal(KRequestPending, status.Int());
+	
+	// stress system and check thrash level and notification
+	ThrashTest(1, ETrue, EWorkloadRandom, 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));
+	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());	
+	User::WaitForAnyRequest();
+	}
+
+void TestThrashHalNotSupported()
+	{
+	test_Equal(KErrNotSupported, UserSvr::HalFunction(EHalGroupVM, EVMHalGetThrashLevel, 0, 0));
+	test_Equal(KErrNotSupported, UserSvr::HalFunction(EHalGroupVM, EVMHalSetThrashThresholds, 0, 0));
+	}
+
+TInt E32Main()
+	{
+	test.Title();
+	test.Start(_L("Test thrashing monitor"));
+
+	test_KErrNone(GetGlobalPolicies());
+
+	TBool flexibleMemoryModel = (MemModelAttributes() & EMemModelTypeMask) == EMemModelTypeFlexible;
+	if (flexibleMemoryModel)
+		TestThrashHal();
+	else
+		TestThrashHalNotSupported();
+	
+	if (gDataPagingSupported && User::CommandLineLength() > 0)
+		{		
+		test.Next(_L("Extended thrashing tests"));
+		TestThrashing();
+		}
+
+	test.End();
+	return 0;
+	}