persistentstorage/dbms/tdbms/t_dbstress.cpp
changeset 0 08ec8eefde2f
child 6 5ffdb8f2067f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/dbms/tdbms/t_dbstress.cpp	Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,600 @@
+// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "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:
+//
+
+#include "t_dbstress.h"
+
+//#define __DUMP_STATE
+#if defined(__WINS__)
+//#define _INSTALL_FILE_SYSTEM
+#endif
+
+GLDEF_D RTest test(_L("t_dbstress: Stress testing DBMS"));
+
+GLDEF_D TPtrC KTestDir=_S("\\DBMS-TST\\");
+GLDEF_D TPtrC KLogFile=_L("T_STRESS.LOG");
+GLDEF_D TPtrC KTestDatabase=_S("T_STRESS.DB");
+GLDEF_D TInt NewCount,OldCount;
+GLDEF_D TInt TransId;
+GLDEF_D Timer RunTimer;
+
+
+LOCAL_D RFs TheFs;
+LOCAL_D RThread TheThread;
+LOCAL_D TRequestStatus TheStatus;
+LOCAL_D RDbStoreDatabase TheDatabase;
+LOCAL_D RDbView Accs;
+LOCAL_D RDbView Trans;
+LOCAL_D RDbTable TheTable;
+LOCAL_D TInt Shot,ShotDuringCommit;
+LOCAL_D TInt64 RunningTime(1);
+LOCAL_D Timer Stopwatch;
+
+#ifdef __DUMP_STATE
+const TPtrC KDumpFile=_S("T_STRESS.DMP");
+#endif
+const TInt KTestCleanupStack=0x20;
+
+void Timer::Start()
+	{
+	iTime.UniversalTime();
+	}
+
+TInt64 Timer::Stop()
+	{
+	TTime t;
+	t.UniversalTime();
+	return ((t.MicroSecondsFrom(iTime).Int64()) + 500)/1000;
+	}
+
+void Timer::Print()
+	{
+	TInt64 milli=Stop();
+	test.Printf(_L("  %u milliseconds\n"), I64LOW(milli) );
+	}
+
+class Set
+	{
+public:
+	struct SColDef
+		{
+		const TDesC* iName;
+		TDbColType iType;
+		TInt iAttributes;
+		};
+public:
+	static CDbColSet* CreateL(const SColDef* aDef);
+	};
+CDbColSet* Set::CreateL(const SColDef* aDef)
+	{
+	CDbColSet *set=CDbColSet::NewLC();
+	for (;aDef->iName!=NULL;++aDef)
+		{
+		TDbCol col(*aDef->iName,aDef->iType);
+		col.iAttributes=aDef->iAttributes;
+		set->AddL(col);
+		}
+	CleanupStack::Pop();
+	return set;
+	}
+
+// Accounts table
+const TPtrC KAccounts=_S("ACCOUNTS");
+const TPtrC KAccountsID=_S("ID");
+const TPtrC KAccountsBalance=_S("BALANCE");
+const TPtrC KAccountsStatement=_S("STATEMENT_BALANCE");
+Set::SColDef const AccountsDef[]=
+	{
+	{&KAccountsID,EDbColInt32,TDbCol::ENotNull},
+	{&KAccountsBalance,EDbColInt32,TDbCol::ENotNull},
+	{&KAccountsStatement,EDbColInt32,TDbCol::ENotNull},
+	{0}
+	};
+const TInt KInitialCash=100000;
+const TInt KInitialBalance=1000;
+
+// Transaction table
+const TPtrC KTransactions=_S("TRANSACTIONS");
+const TPtrC KTransactionDate=_S("T_DATE");
+const TPtrC KTransactionFrom=_S("FROM_ID");
+const TPtrC KTransactionTo=_S("TO_ID");
+const TPtrC KTransactionAmount=_S("AMOUNT");
+Set::SColDef const TransactionsDef[]=
+	{
+//	{&KTransactionDate,EDbColDateTime,TDbCol::ENotNull},
+	{&KTransactionDate,EDbColInt32,TDbCol::ENotNull},
+	{&KTransactionFrom,EDbColInt32,TDbCol::ENotNull},
+	{&KTransactionTo,EDbColInt32,TDbCol::ENotNull},
+	{&KTransactionAmount,EDbColInt32,TDbCol::ENotNull},
+	{0}
+	};
+
+LOCAL_D TInt32 TotalMonies;
+LOCAL_D TBuf<100> Buf;
+
+GLDEF_C TInt Random(TInt aRange)
+	{
+	return (Math::Random()>>11)%aRange;
+	}
+
+#undef test
+LOCAL_C void Test(TInt aValue,TInt anExpected,TInt aLine)
+	{
+	if (aValue==anExpected)
+		return;
+	test.Printf(_L("** Expected %d, was %d\n"),anExpected,aValue);
+	test(EFalse,aLine);
+	}
+#define test1(aTest) test(aTest,__LINE__)
+#define test2(aValue,anExpected) Test(aValue,anExpected,__LINE__)
+
+LOCAL_C void CreateIndexL(RDbDatabase& aDatabase,const TDesC& aTable,const TDesC& aColumn,TBool aUnique)
+	{
+	CDbKey* key=CDbKey::NewLC();
+	key->AddL(aColumn);
+	if (aUnique)
+		key->MakeUnique();
+	test2(aDatabase.CreateIndex(aColumn,aTable,*key),KErrNone);
+	CleanupStack::PopAndDestroy();
+	}
+
+//
+// Create the database
+//
+LOCAL_C void CreateDatabaseL()
+	{
+	CFileStore* store=CPermanentFileStore::ReplaceLC(TheFs,KTestDatabase,EFileRead|EFileWrite);
+	store->SetTypeL(KPermanentFileStoreLayoutUid);
+	store->SetRootL(TheDatabase.CreateL(store));
+//	create the tables
+	TheDatabase.Begin();
+	CDbColSet* set=Set::CreateL(AccountsDef);
+	test2(TheDatabase.CreateTable(KAccounts,*set),KErrNone);
+	delete set;
+	CreateIndexL(TheDatabase,KAccounts,KAccountsID,ETrue);
+	CreateIndexL(TheDatabase,KAccounts,KAccountsBalance,EFalse);
+	set=Set::CreateL(TransactionsDef);
+	test2(TheDatabase.CreateTable(KTransactions,*set),KErrNone);
+	delete set;
+	CreateIndexL(TheDatabase,KTransactions,KTransactionDate,EFalse);
+	test2(TheDatabase.Commit(),KErrNone);
+	OldCount=NewCount=0;
+// prepare Accs table
+	TheDatabase.Begin();
+	test2(Accs.Prepare(TheDatabase,_L("select * from accounts"),Accs.EInsertOnly),KErrNone);
+	Accs.InsertL();
+	Accs.SetColL(1,TInt32(ECash));
+	Accs.SetColL(2,KInitialCash);
+	Accs.SetColL(3,KInitialCash);
+	Accs.PutL();
+	TotalMonies=KInitialCash;
+	for (TInt ii=EJohn;ii<=EPenny;++ii)
+		{
+		Accs.InsertL();
+		Accs.SetColL(1,ii);
+		Accs.SetColL(2,KInitialBalance);
+		Accs.SetColL(3,KInitialBalance);
+		Accs.PutL();
+		TotalMonies+=KInitialBalance;
+		}
+	test2(TheDatabase.Commit(),KErrNone);
+	Accs.Close();
+	TheDatabase.Close();
+	CleanupStack::PopAndDestroy();	// store
+	}
+
+
+#ifdef __DUMP_STATE
+LOCAL_C void DumpStateL()
+	{
+	RFile file;
+	CleanupClosePushL(file);
+	User::LeaveIfError(file.Replace(TheFs,KLogFile,EFileWrite|EFileStreamText));
+	RDbRowSet::RConstraint match;
+	CleanupClosePushL(match);
+	for (TInt id=ECash;id<=EPenny;++id)
+		{
+		Buf.Format(_L("id=%d"),id);
+		Accs.FirstL();
+		test1(Accs.FindL(Accs.EForwards,Buf)>=0);
+		Accs.GetL();
+		TInt balance=Accs.ColInt(2);
+		Buf.Format(_L("\nStatement for account %d: Previous balance %d\n"),id,balance);
+		User::LeaveIfError(file.Write(Buf));
+		Buf.Format(_L("from_id=%d or to_id=%d"),id,id);
+		User::LeaveIfError(match.Open(Trans,Buf));
+		for (Trans.BeginningL();Trans.NextL();)
+			{
+			if (Trans.MatchL(match))
+				{
+				Trans.GetL();
+				TInt from=Trans.ColInt(2);
+				TInt amount=Trans.ColInt(4);
+				Buf.Format(_L("%04d: %6s %5d\n"),Trans.ColInt(1),from==id?_S("debit"):_S("credit"),amount);
+				User::LeaveIfError(file.Write(Buf));
+				if (from==id)
+					balance-=amount;
+				else
+					balance+=amount;
+				}
+			}
+		Buf.Format(_L("Closing balance %d\n"),balance);
+		User::LeaveIfError(file.Write(Buf));
+		Buf.Format(_L("Account balance %d\n"),Accs.ColInt(3));
+		User::LeaveIfError(file.Write(Buf));
+		}
+	CleanupStack::PopAndDestroy(2);
+	test1(0);
+	}
+#endif
+
+//
+// Check that the database structure is fully intact
+//
+LOCAL_C void VerifyDatabaseL(CPersistentStore& aStore)
+	{
+	TheDatabase.OpenL(&aStore,aStore.Root());
+// check any indexes
+	test2(TheTable.Open(TheDatabase,KAccounts,TheTable.EReadOnly),KErrNone);
+	test2(TheTable.CountL(),KAccountIDs);
+	TInt r=TheTable.SetIndex(KAccountsID);
+	if (r!=KErrCorrupt)
+		{
+		test2(r,KErrNone);
+		test2(TheTable.CountL(),KAccountIDs);
+		for (TInt id=ECash;id<=EPenny;++id)
+			{
+			test1(TheTable.NextL());
+			TheTable.GetL();
+			test2(TheTable.ColInt(1),id);
+			}
+		test1(!TheTable.NextL());
+		}
+	r=TheTable.SetIndex(KAccountsBalance);
+	if (r!=KErrCorrupt)
+		{
+		test2(r,KErrNone);
+		test2(TheTable.CountL(),KAccountIDs);
+		test1(TheTable.FirstL());
+		TheTable.GetL();
+		TInt last=TheTable.ColInt(2);
+		for (TInt ii=1;ii<KAccountIDs;++ii)
+			{
+			test1(TheTable.NextL());
+			TheTable.GetL();
+			TInt bal=TheTable.ColInt(2);
+			test1(bal>=last);
+			last=bal;
+			}
+		test1(!TheTable.NextL());
+		}
+	TheTable.Close();
+	test2(TheTable.Open(TheDatabase,KTransactions,TheTable.EReadOnly),KErrNone);
+	TInt count=TheTable.CountL();
+	r=TheTable.SetIndex(KTransactionDate);
+	if (r!=KErrCorrupt)
+		{
+		test2(r,KErrNone);
+		test2(TheTable.CountL(),count);
+		if (count)
+			{
+			test1(TheTable.FirstL());
+			TheTable.GetL();
+			TInt last=TheTable.ColInt(1);
+			while (--count!=0)
+				{
+				test1(TheTable.NextL());
+				TheTable.GetL();
+				TInt date=TheTable.ColInt(1);
+				test1(date>last);
+				last=date;
+				}
+			test1(!TheTable.NextL());
+			}
+		else
+			test1(!TheTable.FirstL());
+		}
+	TheTable.Close();
+// verify database integrity
+	TInt balance[KAccountIDs];
+	test2(Accs.Prepare(TheDatabase,_L("select id,statement_balance,balance from accounts"),Accs.EReadOnly),KErrNone);
+	test2(Accs.CountL(),KAccountIDs);
+	while (Accs.NextL())
+		{
+		Accs.GetL();
+		TInt id=Accs.ColInt(1);
+		balance[id]=Accs.ColInt(2);
+		}
+	test2(Trans.Prepare(TheDatabase,_L("select t_date,from_id,to_id,amount from Transactions"),Trans.EReadOnly),KErrNone);
+	TInt transact=0;
+	while (Trans.NextL())
+		{
+		++transact;
+		Trans.GetL();
+		TInt from=Trans.ColInt(2);
+		TInt to=Trans.ColInt(3);
+		TInt amount=Trans.ColInt(4);
+		balance[from]-=amount;
+		balance[to]+=amount;
+		}
+	test2(transact,Trans.CountL());
+	if (NewCount!=-1 && transact!=NewCount)
+		{
+		test2(transact,OldCount);
+		++ShotDuringCommit;
+		}
+	OldCount=NewCount=transact;
+	TInt total=0;
+	for (Accs.BeginningL();Accs.NextL();)
+		{
+		Accs.GetL();
+		TInt id=Accs.ColInt(1);
+#ifdef __DUMP_STATE
+		if (balance[id]!=Accs.ColInt(3))
+			DumpStateL();
+#else
+		test(balance[id]==Accs.ColInt(3));
+#endif
+		total+=balance[id];
+		}
+	test2(total,TotalMonies);
+	Trans.Close();
+	Accs.Close();
+	TheDatabase.Close();
+	}
+
+LOCAL_C TInt Verify(CPersistentStore& aStore)
+	{
+	TRAPD(r,VerifyDatabaseL(aStore));
+	return r;
+	}
+
+LOCAL_C TInt Recover(CPersistentStore& aStore)
+	{
+	TRAPD(r,TheDatabase.OpenL(&aStore,aStore.Root()));
+	if (r==KErrNone)
+		{
+		r=TheDatabase.Recover();
+		TheDatabase.Close();
+		}
+	return r;
+	}
+
+LOCAL_C void CompactL(CStreamStore& aStore)
+	{
+	TInt t=aStore.ReclaimL();
+	Stopwatch.Start();
+	t-=aStore.CompactL();
+	test.Printf(_L("  compacted %d byte(s) in"),t);
+	Stopwatch.Print();
+	aStore.CommitL();
+	}
+
+LOCAL_C TInt Compact(CStreamStore& aStore)
+	{
+	TRAPD(r,CompactL(aStore));
+	return r;
+	}
+
+LOCAL_C TInt EndThread()
+	{
+	RunningTime+=RunTimer.Stop();
+	if (TheStatus==KRequestPending)
+		TheThread.Kill(1);
+	User::WaitForRequest(TheStatus);
+	TInt r;
+	if (TheThread.ExitType()==EExitKill)
+		r=TheThread.ExitReason();
+	else
+		r=TheStatus.Int();
+	TheThread.Close();
+	return r;
+	}
+
+//aTestExecutionTime - desired test execution time in minutes
+LOCAL_C void RunTestL(TInt aTestExecutionTime = 0)
+	{
+	__ASSERT_ALWAYS(aTestExecutionTime >= 0, User::Invariant());
+
+	RThread().SetPriority(EPriorityMore);
+	test.Start(_L("Create the database"));
+	CreateDatabaseL();
+
+	TTimeIntervalMinutes timeInterval(aTestExecutionTime);
+
+	TTime timeCurrent;
+	timeCurrent.UniversalTime();
+	TTime timeEnd(timeCurrent);
+	timeEnd += timeInterval;
+
+	for (TBool condition=ETrue; condition; condition = aTestExecutionTime > 0 ? (timeCurrent < timeEnd) : ETrue)
+		{
+		test.Next(_L("Main loop"));
+		test.Start(_L("Kick off the thread"));
+		test2 (StartThread(TheThread,TheStatus),KErrNone);
+		// random delay
+		for (;;)
+			{
+			User::After(95000);
+			if (TheStatus!=KRequestPending)
+				break;
+			if (Random(1000)<30)
+				break;
+			}
+		test.Next(_L("End the thread"));
+		TInt exit=EndThread();
+		if (exit!=1)
+			test.Printf(_L("  thread failed with error %d\n"),exit);
+//
+		++Shot;
+		CFileStore* store=NULL;
+		for (TInt ii=0;;++ii)
+			{
+			test.Printf(_L("Opening %d\r"),ii);
+			TRAPD(r,store=CFileStore::OpenL(TheFs,KTestDatabase,EFileRead|EFileWrite));
+			if (r==KErrNone)
+				break;
+			test (r==KErrInUse);
+			User::After(100000);
+			}
+		test.Next(_L("Verify & Recover"));
+		test2 (Verify(*store),KErrNone);
+		TInt64 tps(TransId);
+		tps*=1000u;
+		tps/=RunningTime;
+		test.Printf(_L("    Iteration %d, TPS %d, during commit %d%%\n"),Shot,I64LOW(tps),(100*ShotDuringCommit)/Shot);
+		TInt r=Recover(*store);
+		if (r==KErrNoMemory || r==KErrDiskFull)
+			{	// need to compact before completing recovery
+			test.Next(_L("No space, compacting"));
+			test2 (Compact(*store),KErrNone);
+			test.Next(_L("Verify & Recover again"));
+			test2 (Verify(*store),KErrNone);
+			r=Recover(*store);
+			}
+		test2 (r,KErrNone);
+		test.Next(_L("Verify & Compact"));
+//		test2 (Verify(*store),KErrNone);
+		test2 (Compact(*store),KErrNone);
+		test.Next(_L("Verify"));
+		test2 (Verify(*store),KErrNone);
+//
+		delete store;
+		test.End();
+
+		timeCurrent.UniversalTime();
+		}
+		test.End();
+	}
+
+/**
+@SYMTestCaseID          SYSLIB-DBMS-CT-0636
+@SYMTestCaseDesc        DBMS stess testing.
+@SYMTestPriority        Medium
+@SYMTestActions         Tests for verifying the database integrity.
+@SYMTestExpectedResults Test must not fail
+@SYMREQ                 REQ0000
+*/
+static void RunVerify()
+	{
+	test.Start(_L(" @SYMTestCaseID:SYSLIB-DBMS-CT-0636 Open store "));
+	CFileStore* store=NULL;
+	TRAPD(r,store=CFileStore::OpenL(TheFs,KTestDatabase,EFileRead|EFileWrite));
+	test2 (r,KErrNone);
+	test.Next(_L("Verify"));
+	NewCount=-1;
+	TotalMonies=KInitialCash + (EPenny-EJohn+1)*KInitialBalance;
+	test2 (Verify(*store),KErrNone);
+	test.Next(_L("Recover"));
+	test2 (Recover(*store),KErrNone);
+	test.Next(_L("Verify"));
+	test2 (Verify(*store),KErrNone);
+	delete store;
+	test.End();
+	}
+
+//
+// Prepare the test directory.
+//
+LOCAL_C void setupTestDirectory()
+    {
+	TInt r=TheFs.Connect();
+	test2(r,KErrNone);
+//
+	r=TheFs.MkDir(KTestDir);
+	test1(r==KErrNone || r==KErrAlreadyExists);
+	r=TheFs.SetSessionPath(KTestDir);
+	test2(r,KErrNone);
+	}
+
+LOCAL_C CTrapCleanup* setupCleanup()
+//
+// Initialise the cleanup stack.
+//
+    {
+	CTrapCleanup* cleanup=CTrapCleanup::New();
+	test1(cleanup!=NULL);
+	TRAPD(r,\
+		{\
+		for (TInt i=KTestCleanupStack;i>0;i--)\
+			CleanupStack::PushL((TAny*)0);\
+		CleanupStack::Pop(KTestCleanupStack);\
+		});
+	test2(r,KErrNone);
+	return cleanup;
+	}
+
+//
+// entry point
+//
+// Parameters usage:
+//	t_stress [-v]|[0]|[<positive number>]
+// Where:
+//	-v - a verification test will be run;
+//	0  - a stress test will be run for indefinite time;
+//	<positive number>  - a stress test will be run for <positive number> minutes;
+// If the test is run without arguments, the test execution time will be 10 minutes
+// (KDefaultTestExecutionTime constant bellow).
+GLDEF_C TInt E32Main()
+    {
+	test.Title();
+	setupTestDirectory();
+	CTrapCleanup* cleanup=setupCleanup();
+	__UHEAP_MARK;
+//
+	TBuf<100> cmd;
+    User::CommandLine(cmd);
+	TLex lex(cmd);
+	TInt err = KErrNone;
+	for(;;)
+		{
+		TPtrC arg(lex.NextToken());
+		if(arg.Length() == 0)
+			{
+			const TInt KDefaultTestExecutionTime = 10;//default test execution time - minutes
+			TRAP(err, RunTestL(KDefaultTestExecutionTime));
+			break;
+			}
+		else if(arg.CompareF(_L("-v")) == 0)
+			{
+			RunVerify();
+			break;
+			}
+		else
+			{
+			TInt32 testExecutionTime = 0;
+			lex.Assign(arg);
+			(void)lex.Val(testExecutionTime);
+			TRAP(err, RunTestL(testExecutionTime));
+			break;
+			}
+		}
+	TInt err2 = TheFs.Delete(KTestDatabase);
+	if(err2 != KErrNone)
+		{
+		RDebug::Print(_L("Error %d deleting \"%S\" file.\n"), err2, &KTestDatabase);
+		}
+	err2 = TheFs.Delete(KLogFile);
+	if(err2 != KErrNone)
+		{
+		RDebug::Print(_L("Error %d deleting \"%S\" file.\n"), err2, &KLogFile);
+		}
+	test2(err, KErrNone);
+//
+	__UHEAP_MARKEND;
+	delete cleanup;
+	TheFs.Close();
+	test.Close();
+	return 0;
+    }