diff -r a179b74831c9 -r c1f20ce4abcf kerneltest/e32test/misc/t_loadsim.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kerneltest/e32test/misc/t_loadsim.cpp Tue Aug 31 16:34:26 2010 +0300 @@ -0,0 +1,2351 @@ +// Copyright (c) 2009-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\misc\t_loadsim.cpp +// +// + + +//------------------------------------------------------------------------------------------- +//! @SYMTestCaseID KBASE-t_loadsim-2705 +//! @SYMTestCaseDesc verifying the behaviour of the load balancer +//! @SYMPREQ 417-52765/417-58889 +//! @SYMTestPriority Critical +//! @SYMTestActions +//! 1. This test runs a variety of loads on an SMP system. Loads types are: +//! 1.1 Cpu intensive loads +//! 1.2 memory intensive loads (high locality) +//! 1.3 memory intensive loads (low locality) +//! 1.4 memory intensive loads with atomic operations +//! 1.5 cpu intensive loads with some serialization +//! 2. For each test, the load is first run on a single cpu locked thread as a baseline +//! benchmark. Then the tests are run in the following configurations: +//! 2.1 For n = 1 to 2*Number of cpus do a run with i threads. +//! 2.2 For h = 1 to NumCpus ; For n = h to 2*NumCpus; run with h high priorty threads and +//! n standard priority threads, with high priority threads cpu locked. +//! 2.3 For h = 1 to NumCpus ; For n = h to 2*NumCpus; run with h high priorty threads and +//! n standard priority threads. +//! @SYMTestExpectedResults +//! test passed. TTest is manual: +//! 1. For each test we expect to see that the amount of CPU time obtained by each CPU is +//! balanced. That is, all standard priority threads get roughly same amount of CPU time +//! and all high priority threads get roughly same amount of CPU time and a higher value +//! than lower priority threads. +//! 2. We also expect the relative efficiency reported by the test between the benchmark +//! and each test run to be >=95% on average. Values well below this are acceptable in +//! test runs involving atomic operations (1.4) + +//------------------------------------------------------------------------------------------- + +#define __E32TEST_EXTENSION__ +#include +#include +#include +#include +#include +#include + +//#define TRACE(x) x +#define TRACE(x) + +void Panic(TInt aLine) + { + User::Panic(_L("T_LOADSIM"),aLine); + } + +#define assert(x) ((void)((x)||(Panic(__LINE__),0))) + +RTest test(_L("T_LOADSIM")); + +const TInt KErrCouldNotStart = -99; + +volatile TInt STFU = 1; + +/****************************************************************************** + * Random Number Generation + ******************************************************************************/ +void LFSR(TUint64& a) + { + TInt i; + for (i=64; i>0; --i) + { + TUint64 x = a<<1; + TUint64 y = x<<1; + x^=y; + a = (y>>1) | (x>>63); + } + } + +// Returns 256*log2(a/2^64) +TInt Log2(TUint64 a) + { + const TUint64 KBit63 = UI64LIT(0x8000000000000000); + TInt n = __e32_find_ms1_64(a); + a <<= (63-n); + n -= 64; + TInt i; + for (i=0; i<8; ++i) + { + a >>= 32; + a *= a; + n <<= 1; + if (a & KBit63) + { + ++n; + } + else + { + a <<= 1; + } + } + return n; + } + +TUint32 ExpRV(TUint64 aU, TUint32 aMean, TUint32 aTick) + { + TInt n = -Log2(aU); + TUint64 x = TUint64(n) * TUint64(aMean); + x *= TUint64(22713); // 2^15 * ln2 + TUint64 p(aTick); + p <<= 22; + x += p; + p += p; + x /= p; + return I64LOW(x); + } + + + +/****************************************************************************** + * Generic High-Resolution Timing + ******************************************************************************/ +class TTimestamp + { +public: + typedef void (*TSampleFunc)(TAny*); +public: + void Sample(); + void Sample(TSampleFunc aFunc, TAny* aPtr); + TInt64 operator-(const TTimestamp&) const; + static void Init(); +private: + TUint32 iF; // User::FastCounter() value + TUint32 iN; // User::NTickCount() value +private: + static TUint32 FF; // User::FastCounter() frequency + static TUint32 NP; // User::NTickCount() period + static TBool FU; // User::FastCounter() counts up + static TUint32 FWrapM; // Number of nanokernel ticks for FastCounter() to wrap / 2 * 2^FWrapS + static TInt FWrapS; // Shift so that 2^31<=FWrapM<2^32 + }; + +TUint32 TTimestamp::FF; +TUint32 TTimestamp::NP; +TBool TTimestamp::FU; +TUint32 TTimestamp::FWrapM; +TInt TTimestamp::FWrapS; + + +void TTimestamp::Sample() + { + TUint32 n = User::NTickCount(); + do { + iN = n; + iF = User::FastCounter(); + n = User::NTickCount(); + } while (n!=iN); + } + +void TTimestamp::Sample(TSampleFunc aFunc, TAny* aPtr) + { + TUint32 n = User::NTickCount(); + do { + iN = n; + (*aFunc)(aPtr); + iF = User::FastCounter(); + n = User::NTickCount(); + } while (n!=iN); + } + +// return (x*a)/b +TUint64 scale(TUint64 x, TUint32 a, TUint32 b) + { + TUint64 mask = KMaxTUint32; + TUint64 x0 = x & mask; + TUint64 x1 = x >> 32; + x0 *= TUint64(a); + x1 *= TUint64(a); + x1 += (x0 >> 32); + x0 &= mask; + TUint64 q1 = x1 / TUint64(b); + TUint64 q0 = x1 - q1*TUint64(b); + q0 <<= 32; + q0 |= x0; + q0 /= TUint64(b); + return (q1<<32)|q0; + } + + +// Return difference between a and this in microseconds +TInt64 TTimestamp::operator-(const TTimestamp& a) const + { + TInt sign = 1; + TTimestamp start; + TTimestamp end; + if (iN-a.iN >= 0x80000000u) + { + sign = -1; + start = *this; + end = a; + } + else + { + start = a; + end = *this; + } + TUint32 fd32 = end.iF - start.iF; + if (!FU) + fd32 = ~fd32 + 1u; + TUint64 nd = TUint64(end.iN) - TUint64(start.iN); + nd <<= 31; // 2^31 * difference in NTickCount + TUint64 x = TUint64(fd32) * TUint64(FWrapM); + x >>= FWrapS; // ftick difference * (FWrapM/2^FWrapS) = 2^31 * ntick difference + nd -= x; // Should now be a multiple of 2^31N where N=2^32*ftickp/ntickp + // i.e. should be a multiple of 2^63*ftickp/ntickp + + // FWrapM = 2^(31+FWrapS)*ftickp/ntickp + // FWrapM << (32-FWrapS) = 2^63*ftickp/ntickp + TUint64 m = TUint64(FWrapM) << (32-FWrapS); + + nd += (m>>1); + nd /= m; + + nd = (nd<<32) + TUint64(fd32); // final result in fast counter ticks + TInt64 r = scale(nd, 1000000, FF); // convert to microseconds + if (sign<0) + r = -r; + return r; + } + +void TTimestamp::Init() + { + TInt r; + r = HAL::Get(HAL::ENanoTickPeriod, (TInt&)NP); + assert(r==KErrNone); + r = HAL::Get(HAL::EFastCounterFrequency, (TInt&)FF); + assert(r==KErrNone); + r = HAL::Get(HAL::EFastCounterCountsUp, (TInt&)FU); + assert(r==KErrNone); + TReal fpn = TReal(FF) * TReal(NP) / 1000000.0; // fast counter ticks per NTick + TReal fwrap = 2147483648.0 / fpn; // NTicks between fast counter wraparounds / 2 + TInt exp = 0; + while (fwrap < 2147483648.0) + { + fwrap *= 2.0; + ++exp; + } + fwrap += 0.5; + if (fwrap >= 4294967296.0) + { + fwrap *= 0.5; + --exp; + } + FWrapM = (TUint32)fwrap; + FWrapS = exp; // NTicks for 2^31 fast ticks = FWrapM/2^FWrapS + + test.Printf(_L("FastCounter frequency %uHz\n"), FF); + if (FU) + test.Printf(_L("FastCounter counts UP\n")); + else + test.Printf(_L("FastCounter counts DOWN\n")); + test.Printf(_L("Nanokernel tick period %uus\n"), NP); + test.Printf(_L("FWrapM %08x\n"), FWrapM); + test.Printf(_L("FWrapS %d\n"), FWrapS); + } + +/****************************************************************************** + * CPU Usage Measurement + ******************************************************************************/ +class TThreadCpuUsageSample + { +public: + void Sample(RThread aThread); + TInt64 ElapsedTimeDelta(const TThreadCpuUsageSample& aStart) const; + TInt64 CpuTimeDelta(const TThreadCpuUsageSample& aStart) const; +private: + static void SampleThreadCpuTime(TAny* aPtr); +private: + TTimestamp iElapsedTime; + TInt64 iCpuTime; + RThread iThread; + }; + +void TThreadCpuUsageSample::Sample(RThread aThread) + { + iThread = aThread; + iElapsedTime.Sample(&SampleThreadCpuTime, this); + } + +void TThreadCpuUsageSample::SampleThreadCpuTime(TAny* aPtr) + { + TThreadCpuUsageSample& me = *(TThreadCpuUsageSample*)aPtr; + TTimeIntervalMicroSeconds& rt = *(TTimeIntervalMicroSeconds*)&me.iCpuTime; + assert(me.iThread.GetCpuTime(rt) == KErrNone); + } + +TInt64 TThreadCpuUsageSample::ElapsedTimeDelta(const TThreadCpuUsageSample& aStart) const + { + return iElapsedTime - aStart.iElapsedTime; + } + +TInt64 TThreadCpuUsageSample::CpuTimeDelta(const TThreadCpuUsageSample& aStart) const + { + return iCpuTime - aStart.iCpuTime; + } + +class TCpuUsage + { +public: + enum {EMaxCpus=8}; +public: + void Sample(); + TInt64 ElapsedTimeDelta(const TCpuUsage& aStart) const; + TInt64 CpuTimeDelta(const TCpuUsage& aStart, TInt aCpu) const; + static TInt N() { return NumberOfCpus; } +public: + static void Init(); +private: + static void SampleIdleTimes(TAny* aPtr); +private: + TTimestamp iElapsedTime; + TInt64 iIdleTime[EMaxCpus]; +private: + static TInt NumberOfCpus; + static RThread IdleThread[EMaxCpus]; + }; + +TInt TCpuUsage::NumberOfCpus = -1; +RThread TCpuUsage::IdleThread[TCpuUsage::EMaxCpus]; + +void TCpuUsage::Init() + { + TTimestamp::Init(); + + NumberOfCpus = UserSvr::HalFunction(EHalGroupKernel, EKernelHalNumLogicalCpus, 0, 0); + test.Printf(_L("NumberOfCpus = %d\n"), NumberOfCpus); + assert(NumberOfCpus > 0); + assert(NumberOfCpus <= EMaxCpus); + + TTimeIntervalMicroSeconds ms; + TInt r; + r = RThread().GetCpuTime(ms); + if (r != KErrNone) + { + test.Printf(_L("RThread::GetCpuTime() returned %d\n"), r); + test.Printf(_L("This test requires a working RThread::GetCpuTime() to run\n")); + test(0); + } + + TFullName kname; + _LIT(KLitKernelName, "ekern.exe*"); + _LIT(KLitNull, "::Null"); + TFindProcess fp(KLitKernelName); + test_KErrNone(fp.Next(kname)); + test.Printf(_L("Found kernel process: %S\n"), &kname); + kname.Append(KLitNull); + TInt i; + for (i=0; i0) + tname.AppendNum(i); + TFindThread ft(tname); + test_KErrNone(ft.Next(tname2)); + TInt r = IdleThread[i].Open(ft); + test_KErrNone(r); + IdleThread[i].FullName(tname2); + test.Printf(_L("Found and opened %S\n"), &tname2); + } + } + +void TCpuUsage::Sample() + { + iElapsedTime.Sample(&SampleIdleTimes, this); + } + +void TCpuUsage::SampleIdleTimes(TAny* aPtr) + { + TCpuUsage& me = *(TCpuUsage*)aPtr; + assert(NumberOfCpus > 0); + TInt i; + for (i=0; i= NumberOfCpus) + return 0; + TInt64 idle_time = iIdleTime[aCpu] - aStart.iIdleTime[aCpu]; + TInt64 elapsed_time = iElapsedTime - aStart.iElapsedTime; + return elapsed_time - idle_time; + } + + + +/****************************************************************************** + * Generic CPU Consumer + ******************************************************************************/ +enum TCpuEaterType + { + EEaterStd =0, // do CPU-intensive work with few memory references + EEaterMemoryLocalS =1, // do loads of memory references with reasonable locality, shared + EEaterMemoryNonLocalS =2, // do loads of memory references with poor locality, shared + EEaterMemoryLocalU =3, // do loads of memory references with reasonable locality, unshared + EEaterMemoryNonLocalU =4, // do loads of memory references with poor locality, unshared + EEaterMemoryAtomic =5, // do loads of atomic memory references + EEaterMemoryAtomic2 =6, // do loads of atomic memory references + EEaterAmdahl =7, // do CPU-intensive work interspersed with serialized sections + }; + +class CDefaultCpuEater; + +class REaterArray; +class MCpuEater + { +public: + MCpuEater(); + virtual ~MCpuEater(); + virtual void Eat(TInt aTime, TUint32* aWorkDone)=0; + virtual void Calibrate(); + inline TBool IsCalibrated() { return iCalibration!=0; } +protected: + TUint32 WorkValue(TInt aTime); + TUint32 iCalibration; // work value for 2^16 microseconds + TUint16 iInstance; + TUint16 iType; + + friend class REaterArray; + }; + +MCpuEater::MCpuEater() + { + iCalibration = 0; // uncalibrated + iInstance = KMaxTUint16; // dummy value + iType = KMaxTUint16; // dummy value + } + +MCpuEater::~MCpuEater() + { + } + +// Calibration is for 2^KLog2CalibrateTime microseconds +const TInt KLog2CalibrateTime = 13; + +TUint32 MCpuEater::WorkValue(TInt aTime) + { + if (iCalibration == 0) + return aTime; + TUint64 x = TUint64(aTime) * TUint64(iCalibration); + x >>= (KLog2CalibrateTime + 2); // Factor of 4 margin for slowdowns + TUint32 r = I64LOW(x); + if (I64HIGH(x)) + r = KMaxTUint32; + if (r == 0) + return 1; + if (r > iCalibration) + return iCalibration; + return r; + } + +void MCpuEater::Calibrate() + { + iCalibration = 0; + TUint32 work = 1; + TUint64 used = 1; + TUint64 threshold = 1; + threshold <<= KLog2CalibrateTime; + while (work) + { + TThreadCpuUsageSample initial; + TThreadCpuUsageSample final; + initial.Sample(RThread()); + Eat(work, 0); + final.Sample(RThread()); + used = final.CpuTimeDelta(initial); + if (used >= threshold) + break; + work <<= 1; + } + assert(work > 0); + TUint64 c(work); + c <<= KLog2CalibrateTime; + c /= used; + if (I64HIGH(c)) + iCalibration = KMaxTUint32; + else if (I64LOW(c)) + iCalibration = I64LOW(c); + else + iCalibration = 1; + test.Printf(_L("MCpuEater::Calibrate() %u\n"), iCalibration); + } + + +class REaterArray : public RPointerArray + { +public: + REaterArray(); + void Close(); + MCpuEater* Find(TInt aType, TInt aInstance); + MCpuEater* FindOrCreateL(TInt aType, TInt aInstance); +private: + MCpuEater* CreateLC(TInt aType); +private: + class MDummy : public MCpuEater + { + public: + MDummy(TInt aType, TInt aInstance) + { iType=TUint16(aType); iInstance=TUint16(aInstance); } + virtual ~MDummy() + {} + virtual void Eat(TInt, TUint32*) + {} + }; +private: + static TBool Identity(const MCpuEater& aL, const MCpuEater& aR); + static TInt Ordering(const MCpuEater& aL, const MCpuEater& aR); + }; + +REaterArray::REaterArray() + : RPointerArray(8, 2*256) + { + } + +void REaterArray::Close() + { + ResetAndDestroy(); + } + +TBool REaterArray::Identity(const MCpuEater& aL, const MCpuEater& aR) + { + return (aL.iType==aR.iType && aL.iInstance==aR.iInstance); + } + +TInt REaterArray::Ordering(const MCpuEater& aL, const MCpuEater& aR) + { + if (aL.iType > aR.iType) + return 1; + if (aL.iType < aR.iType) + return -1; + if (aL.iInstance > aR.iInstance) + return 1; + if (aL.iInstance < aR.iInstance) + return -1; + return 0; + } + +MCpuEater* REaterArray::Find(TInt aType, TInt aInstance) + { + MDummy search(aType, aInstance); + TInt ix = FindInOrder(&search, &Ordering); + if (ix < 0) + return 0; + return (*this)[ix]; + } + +MCpuEater* REaterArray::FindOrCreateL(TInt aType, TInt aInstance) + { + MCpuEater* p = Find(aType, aInstance); + if (p) + return p; + p = CreateLC(aType); + p->iType = TUint16(aType); + p->iInstance = TUint16(aInstance); + InsertInOrderL(p, &Ordering); + CleanupStack::Pop(); + return p; + } + +/****************************************************************************** + * Generic zero-drift timed events + ******************************************************************************/ +class CLoadSim; +class MEvent + { +public: + MEvent(CLoadSim*, TInt); + virtual void Start()=0; + virtual ~MEvent(); + inline TBool Queued() const + { return iQueued; } +protected: + void QueueAt(TUint32 aTime); + void QueueAfter(TUint32 aInterval); + void Dequeue(); + virtual TInt Event(); + inline TUint64 Random(); +protected: + TUint8 iId; + TUint8 iQueued; + TUint8 iE1; + TUint8 iE2; + MEvent* iChain; + CLoadSim* iT; + TUint32 iNextEventTime; + friend class CLoadSim; + }; + +class CLoadSim : public CActive + { +public: + static CLoadSim* NewL(); + ~CLoadSim(); + inline TInt TimerPeriod() const + { return iTimerPeriod; } + TUint64 Random(); +private: + CLoadSim(); + virtual void RunL(); + virtual void DoCancel(); + void StartTimer(); +private: + RTimer iTimer; + TUint64 iSeed; + MEvent* iNextEvent; + TUint32 iIterations; + TUint32 iLastTrigger; // Last trigger time in ticks + TInt iCarry; + TInt iTimerPeriod; // Timer tick period in microseconds + TInt iMaxDelta; + TUint8 iInRunL; + TUint8 iTimerRunning; + TUint8 iTimerInit; + TUint8 iOffsetInit; + TUint32 iOffset; +private: + friend class MEvent; + }; + +inline TUint64 MEvent::Random() + { return iT->Random(); } + +CLoadSim::CLoadSim() + : CActive(EPriorityStandard) + { + iSeed = 0xadf85458; + assert(HAL::Get(HAL::ENanoTickPeriod, iTimerPeriod)==KErrNone); + iMaxDelta = KMaxTInt / (2*iTimerPeriod); + } + +CLoadSim::~CLoadSim() + { + Cancel(); + iTimer.Close(); + } + +CLoadSim* CLoadSim::NewL() + { + CLoadSim* p = new (ELeave) CLoadSim(); + CleanupStack::PushL(p); + User::LeaveIfError(p->iTimer.CreateLocal()); + CleanupStack::Pop(); + return p; + } + +void CLoadSim::DoCancel() + { + iTimer.Cancel(); + iTimerRunning = 0; + } + +void CLoadSim::RunL() + { + TRACE(RDebug::Printf("!%d\n", iStatus.Int())); + iTimerRunning = 0; + iInRunL = 1; + TUint32 now = iLastTrigger; + if (iStatus == KErrNone) + { + now += iCarry; + iLastTrigger = now; + } + else if (iStatus == KErrArgument) + { + now += iCarry; + } + else if (iStatus == KErrCancel) + { + iLastTrigger += iCarry; // trigger time was still updated + } + iCarry = 0; + MEvent* e = 0; + FOREVER + { + ++iIterations; + e = iNextEvent; + if (!e || e->iNextEventTime>now) + break; + iNextEvent = e->iChain; + e->iChain = 0; + e->iQueued = 0; + e->Event(); + } + if (e) + { + TInt delta = TInt(e->iNextEventTime - iLastTrigger); + if (delta > iMaxDelta) + delta = iMaxDelta; + if (delta < -iMaxDelta) + delta = -iMaxDelta; + iCarry = delta; + TInt us = delta * iTimerPeriod; + TRACE(RDebug::Printf("T+%d\n", us)); + iTimer.AgainHighRes(iStatus, us); + SetActive(); + iTimerRunning = 1; + } + iInRunL = 0; + } + +void CLoadSim::StartTimer() + { + if (iInRunL) + return; + if (iTimerRunning) + { + TRACE(RDebug::Printf("TC\n")); + iTimer.Cancel(); // will cause RunL with KErrCancel which will restart timer + return; + } + TInt delta = TInt(iNextEvent->iNextEventTime - iLastTrigger); + if (delta > iMaxDelta) + delta = iMaxDelta; + if (delta < -iMaxDelta) + delta = -iMaxDelta; + iCarry = delta; + TInt us = delta * iTimerPeriod; + if (iTimerInit) + { + TRACE(RDebug::Printf("sT+%d\n", us)); + iTimer.AgainHighRes(iStatus, us); + } + else + { + if (!iOffsetInit) + iOffsetInit=1, iOffset=User::NTickCount(); + TRACE(RDebug::Printf("sT++%d\n", us)); + iTimer.HighRes(iStatus, us); + iTimerInit = 1; + } + SetActive(); + iTimerRunning = 1; + } + +TUint64 CLoadSim::Random() + { + LFSR(iSeed); + TUint32 h = I64HIGH(iSeed); + TUint32 l = I64LOW(iSeed); + h *= 0x9e3779b9u; + l *= 0x9e3779b9u; + return MAKE_TUINT64(l,h); + } + +MEvent::MEvent(CLoadSim* aT, TInt aId) + { + iId = (TUint8)aId; + iQueued = 0; + iE1 = 0; + iE2 = 0; + iChain = 0; + iT = aT; + iNextEventTime = 0; + } + +MEvent::~MEvent() + { + if (iT) + Dequeue(); + } + +void MEvent::QueueAt(TUint32 aTime) + { + TRACE(RDebug::Printf("Q%d@%u\n", iId, aTime)); + if (iQueued) + Dequeue(); + MEvent** p = &iT->iNextEvent; + MEvent* e = iT->iNextEvent; + for (; e && e->iNextEventTime <= aTime; p=&e->iChain, e=e->iChain) + {} + iChain = e; + *p = this; + iNextEventTime = aTime; + iQueued = 1; + if (iT->iNextEvent==this && !iT->iInRunL) + iT->StartTimer(); + } + +void MEvent::QueueAfter(TUint32 aInterval) + { + TRACE(RDebug::Printf("Q%d+%u\n", iId, aInterval)); + TUint32 now = User::NTickCount(); + if (!iT->iTimerInit) + iT->iOffset=now, iT->iOffsetInit=1; + QueueAt(now-iT->iOffset+aInterval); + } + +void MEvent::Dequeue() + { + TRACE(RDebug::Printf("DQ%d\n", iId)); + if (!iQueued) + return; + MEvent* e = iT->iNextEvent; + for (; e && e->iChain!=this; e=e->iChain) + {} + if (e) + { + e->iChain = iChain; + } + iChain = 0; + iQueued = 0; + } + +TInt MEvent::Event() + { + TRACE(RDebug::Printf("*%d\n", iId)); + return iId; + } + + + +/****************************************************************************** + * Poisson process simulation + ******************************************************************************/ +class MDiscretePoisson : public MEvent + { +public: + MDiscretePoisson(CLoadSim* aT, TInt aId, TUint32 aMicroseconds); + ~MDiscretePoisson(); + virtual void Start(); + virtual TInt Event(); + virtual void PoissonEvent(); +public: + TUint32 iUs; + TBool iContinue; + }; + +MDiscretePoisson::MDiscretePoisson(CLoadSim* aT, TInt aId, TUint32 aMicroseconds) + : MEvent(aT, aId) + { + iUs = aMicroseconds; + iContinue = EFalse; + } + +MDiscretePoisson::~MDiscretePoisson() + { + } + +void MDiscretePoisson::Start() + { + iContinue = ETrue; + TUint32 gap = ExpRV(Random(), iUs, iT->TimerPeriod()); + TRACE(RDebug::Printf("GG%u\n", gap)); + QueueAt(iNextEventTime + gap); + } + +TInt MDiscretePoisson::Event() + { + PoissonEvent(); + if (iContinue) + Start(); + return MEvent::Event(); + } + +void MDiscretePoisson::PoissonEvent() + { + } + + + +/****************************************************************************** + * Consume a specified amount of CPU time in either a continuous + * or 'staccato' fashion (i.e. in irregular intervals punctuated by gaps) + ******************************************************************************/ +class CStaccatoCpuEater : public CActive, public MEvent + { +public: + CStaccatoCpuEater(CLoadSim* aT, MCpuEater* aE, TUint32 aGranularity, TUint32 aMeanGap); + ~CStaccatoCpuEater(); + void EatMore(TInt64 aMicroseconds); + TUint32 WorkDone() const { return iWorkDone; } + TUint32 Invocations() const { return iInvocations; } +private: + virtual void RunL(); + virtual void DoCancel(); + virtual void Start(); + virtual TInt Event(); + void StartEating(); +private: + MCpuEater* iE; + TUint32 iWorkDone; + TUint32 iInvocations; + TUint32 iGranularity; + TUint32 iMeanGap; + TBool iEating; + TInt64 iRemainingCpuTime; + TTimeIntervalMicroSeconds iInitialCpuTime; + TTimeIntervalMicroSeconds iFinalCpuTime; + TInt64 iTotalCpuTime; + }; + +CStaccatoCpuEater::CStaccatoCpuEater(CLoadSim* aT, MCpuEater* aE, TUint32 aGranularity, TUint32 aMeanGap) + : CActive(EPriorityIdle), + MEvent(aT, 0x53) + { + iE = aE; + iWorkDone = 0; + iInvocations = 0; + iGranularity = aGranularity; + iMeanGap = aMeanGap; + iEating = EFalse; + iRemainingCpuTime = 0; + } + +CStaccatoCpuEater::~CStaccatoCpuEater() + { + Cancel(); + } + +void CStaccatoCpuEater::EatMore(TInt64 aMicroseconds) + { + TRACE(RDebug::Printf("E+%08x %08x\n", I64HIGH(aMicroseconds), I64LOW(aMicroseconds))); + iRemainingCpuTime += aMicroseconds; + if (!Queued() && !iEating && iRemainingCpuTime>0) + StartEating(); + } + +void CStaccatoCpuEater::RunL() + { + TInt time = KMaxTInt; + if (iRemainingCpuTime < TInt64(KMaxTInt)) + time = I64LOW(iRemainingCpuTime); + ++iInvocations; + iE->Eat(time, &iWorkDone); + TTimeIntervalMicroSeconds ms; + TInt r = RThread().GetCpuTime(ms); + assert(r==KErrNone); + if (ms < iFinalCpuTime) + { + SetActive(); + TRequestStatus* pS = &iStatus; + User::RequestComplete(pS, 0); + return; + } + iEating = EFalse; + TInt64 delta = ms.Int64() - iInitialCpuTime.Int64(); + iRemainingCpuTime -= delta; + iTotalCpuTime += delta; + TRACE(RDebug::Printf("D=%8u T=%10u\n",I64LOW(delta),I64LOW(iTotalCpuTime))); + if (iRemainingCpuTime > 0) + { + TUint32 gap = ExpRV(Random(), iMeanGap, iT->TimerPeriod()); + TRACE(RDebug::Printf("G%u\n", gap)); + QueueAfter(gap); + } + } + +void CStaccatoCpuEater::DoCancel() + { + MEvent::Dequeue(); + iEating = EFalse; + } + +void CStaccatoCpuEater::Start() + { + } + +TInt CStaccatoCpuEater::Event() + { + if (!iEating && iRemainingCpuTime>0) + { + StartEating(); + } + return MEvent::Event(); + } + +void CStaccatoCpuEater::StartEating() + { + iEating = ETrue; + TInt r = RThread().GetCpuTime(iInitialCpuTime); + assert(r==KErrNone); + if (iGranularity) + { + TInt howmuch = ExpRV(iT->Random(), iGranularity, 1); + TRACE(RDebug::Printf("SE+%08x\n", howmuch)); + iFinalCpuTime = iInitialCpuTime.Int64() + TInt64(howmuch); + } + else + iFinalCpuTime = iInitialCpuTime.Int64() + iRemainingCpuTime; // continuous CPU use + SetActive(); + TRequestStatus* pS = &iStatus; + User::RequestComplete(pS, 0); + } + + + +/****************************************************************************** + * Consume CPU time in a bursty fashion + ******************************************************************************/ +class CBurstyCpuEater : public CStaccatoCpuEater, public MDiscretePoisson + { +public: + struct SParams + { + MCpuEater* iE; + TUint32 iGranularity; + TUint32 iMeanIntraBurstGap; + TUint32 iMeanBurstLength; + TUint32 iMeanInterBurstGap; + }; +public: + CBurstyCpuEater(CLoadSim* aT, const SParams& aParams); + ~CBurstyCpuEater(); + virtual void Start(); + virtual void PoissonEvent(); +public: + TUint32 iMeanBurstLength; + TUint32 iMeanInterBurstGap; + }; + +CBurstyCpuEater::CBurstyCpuEater(CLoadSim* aT, const SParams& aParams) + : CStaccatoCpuEater(aT, aParams.iE, aParams.iGranularity, aParams.iMeanIntraBurstGap), + MDiscretePoisson(aT, 0x42, aParams.iMeanInterBurstGap) + { + iMeanBurstLength = aParams.iMeanBurstLength; + iMeanInterBurstGap = aParams.iMeanInterBurstGap; + } + +CBurstyCpuEater::~CBurstyCpuEater() + { + } + +void CBurstyCpuEater::Start() + { + if (iMeanInterBurstGap > 0) + { + PoissonEvent(); + MDiscretePoisson::Start(); + } + else + { + EatMore(iMeanBurstLength); // one single burst + } + } + +void CBurstyCpuEater::PoissonEvent() + { + TInt burstLen = ExpRV(CStaccatoCpuEater::Random(), iMeanBurstLength, 1); + EatMore(burstLen); + } + + + +/****************************************************************************** + * Stop the active scheduler after a certain time + ******************************************************************************/ +class CTimedStopper : public CActive + { +public: + static CTimedStopper* NewL(); + ~CTimedStopper(); + void Start(TInt64 aMicroseconds); +private: + CTimedStopper(); + virtual void RunL(); + virtual void DoCancel(); +private: + RTimer iTimer; + }; + +CTimedStopper::CTimedStopper() + : CActive(EPriorityHigh) + { + } + +CTimedStopper::~CTimedStopper() + { + Cancel(); + iTimer.Close(); + } + +CTimedStopper* CTimedStopper::NewL() + { + CTimedStopper* p = new (ELeave) CTimedStopper(); + CleanupStack::PushL(p); + User::LeaveIfError(p->iTimer.CreateLocal()); + CleanupStack::Pop(); + return p; + } + +void CTimedStopper::DoCancel() + { + iTimer.Cancel(); + } + +void CTimedStopper::RunL() + { + CActiveScheduler::Stop(); + } + +void CTimedStopper::Start(TInt64 aMicroseconds) + { + TInt p = (TInt)aMicroseconds; + iTimer.HighRes(iStatus, p); + SetActive(); + } + + + + + +/****************************************************************************** + * Do something CPU intensive to consume CPU time + ******************************************************************************/ +class CDefaultCpuEater : public CBase, public MCpuEater + { +public: + CDefaultCpuEater(); + ~CDefaultCpuEater(); + virtual void Eat(TInt aTime, TUint32* aWorkDone); +protected: + TUint64 iX; + }; + +CDefaultCpuEater::CDefaultCpuEater() + { + iX = 1; + } + +CDefaultCpuEater::~CDefaultCpuEater() + { + } + +void CDefaultCpuEater::Eat(TInt aTime, TUint32* aWorkDone) + { + const TUint64 KMagic = UI64LIT(0x9e3779b97f4a7c15); + TUint32 work = WorkValue(aTime); + if (aWorkDone) + *aWorkDone += work; + while (work--) + iX *= KMagic; + } + + + +/****************************************************************************** + * Do something CPU intensive to consume CPU time, partially serialized + ******************************************************************************/ +class CAmdahlCpuEater : public CDefaultCpuEater + { +public: + static CAmdahlCpuEater* NewLC(); + ~CAmdahlCpuEater(); + virtual void Eat(TInt aTime, TUint32* aWorkDone); +protected: + CAmdahlCpuEater(); + void ConstructL(); +protected: + RMutex iMutex; + TUint32 iFactor; + }; + +CAmdahlCpuEater::CAmdahlCpuEater() + { + } + +CAmdahlCpuEater::~CAmdahlCpuEater() + { + iMutex.Close(); + } + +CAmdahlCpuEater* CAmdahlCpuEater::NewLC() + { + CAmdahlCpuEater* p = new (ELeave) CAmdahlCpuEater(); + CleanupStack::PushL(p); + p->ConstructL(); + return p; + } + +void CAmdahlCpuEater::ConstructL() + { + User::LeaveIfError(iMutex.CreateLocal()); + iFactor = KMaxTUint32 / (4*TCpuUsage::N()); + } + +void CAmdahlCpuEater::Eat(TInt aTime, TUint32* aWorkDone) + { + TUint64 t(aTime); + t *= TUint64(iFactor); + t += TUint64(0x80000000u); + t >>= 32; + TInt stime = I64LOW(t); + if (IsCalibrated()) + { + iMutex.Wait(); + CDefaultCpuEater::Eat(stime, aWorkDone); + aTime -= stime; + iMutex.Signal(); + } + CDefaultCpuEater::Eat(aTime, aWorkDone); + } + + + +/****************************************************************************** + * Do something memory intensive to consume CPU time + ******************************************************************************/ +class CMemoryBandwidthEater : public CBase, public MCpuEater + { +public: + static CMemoryBandwidthEater* NewLC(TUint32 aSize, TUint32 aRegionSize, TUint32 aRegionOffset); + ~CMemoryBandwidthEater(); + virtual void Calibrate(); +protected: + CMemoryBandwidthEater(TUint32 aSize, TUint32 aRegionSize, TUint32 aRegionOffset); + void ConstructL(); + virtual void Eat(TInt aTime, TUint32* aWorkDone); + TAny* At(TUint32 aRegion, TUint32 aIndex); + TAny* StepWithinRegion(TAny* aInitial, TUint32 aStep); +protected: + volatile TUint32 iRegionAlloc; + TUint32 iPageSize; + TUint32 iSize; // multiple of page size + TAny* iData; // page aligned + RChunk iChunk; + TUint8 iLog2RegionSize; // log2(bytes per region) + TUint8 iLog2RO; // log2(offset from region n to n+1 in bytes) + TUint8 iLog2PageSize; + TUint8 iRegionBits; // number of bits to specify region + TUint32 iNRgn; + TUint32 iRegionMask; + TUint32 iLowerIndexMask; + TUint32 iUpperIndexMask; + }; + +TUint32 AtomicClearLS1(volatile TUint32* aMask) + { + TUint32 initial = *aMask; + TUint32 final; + do { + final = initial & (initial-1); + } while(!__e32_atomic_cas_ord32(aMask, &initial, final)); + return initial; + } + +TInt AtomicAllocBit(volatile TUint32* aMask) + { + return __e32_find_ls1_32(AtomicClearLS1(aMask)); + } + +TUint32 AtomicFreeBit(volatile TUint32* aMask, TInt aBit) + { + return __e32_atomic_ior_ord32(aMask, 1u<ConstructL(); + return p; + } + +CMemoryBandwidthEater::CMemoryBandwidthEater(TUint32 aSize, TUint32 aRegionSize, TUint32 aRegionOffset) + { + TInt r = HAL::Get(HAL::EMemoryPageSize, (TInt&)iPageSize); + assert(r==KErrNone); + iLog2PageSize = (TUint8)__e32_find_ms1_32(iPageSize); + assert( !(aRegionSize & (aRegionSize-1)) ); + assert( !(aRegionOffset & (aRegionOffset-1)) ); + iLog2RegionSize = (TUint8)__e32_find_ms1_32(aRegionSize); + iLog2RO = (TUint8)__e32_find_ms1_32(aRegionOffset); + TUint32 round = (aRegionSize>iPageSize) ? aRegionSize : iPageSize; + iSize = (aSize + round - 1) &~ (round - 1); + --iSize; + iSize |= (iSize>>1); + iSize |= (iSize>>2); + iSize |= (iSize>>4); + iSize |= (iSize>>8); + iSize |= (iSize>>16); + ++iSize; + iNRgn = iSize >> iLog2RegionSize; + if (iNRgn>=32) + iRegionAlloc = ~0u; + else + iRegionAlloc = ~((~0u)<>(iRegionBits+iLog2RO))<<(iRegionBits+iLog2RO); + } + +CMemoryBandwidthEater::~CMemoryBandwidthEater() + { + iChunk.Close(); + } + +void CMemoryBandwidthEater::ConstructL() + { + TInt mask = (1<<20)-1; + TInt maxSize = (TInt(iSize)+mask)&~mask; + User::LeaveIfError(iChunk.CreateLocal(iSize, maxSize, EOwnerThread)); + iData = iChunk.Base(); + } + +void CMemoryBandwidthEater::Calibrate() + { + MCpuEater::Calibrate(); + MCpuEater::Calibrate(); + } + +TAny* CMemoryBandwidthEater::At(TUint32 aRegion, TUint32 aIndex) + { + TUint32 offset = aIndex & iLowerIndexMask; + offset |= (aRegion<=0); + TUint32 done = 0; + TUint32 rgnsz = 1u << iLog2RegionSize; + for (; work; work-=done) + { + done = (work>rgnsz) ? rgnsz : work; + TUint8* p = (TUint8*)At(region,0); + TUint8 prev = *p; + TUint32 n = done; + do { + TUint8* q = p; + p = (TUint8*)StepWithinRegion(p, 31); + *q = *p; + } while(--n); + *p = prev; + } + AtomicFreeBit(&iRegionAlloc, region); + } + + +/****************************************************************************** + * Do lots of atomic operations to consume CPU time + ******************************************************************************/ +class CAtomicMemoryBandwidthEater : public CMemoryBandwidthEater + { +public: + static CAtomicMemoryBandwidthEater* NewLC(TUint32 aSize); + ~CAtomicMemoryBandwidthEater(); +protected: + CAtomicMemoryBandwidthEater(TUint32 aSize); + void ConstructL(); + virtual void Eat(TInt aTime, TUint32* aWorkDone); +protected: + volatile TUint32 iX; + }; + +CAtomicMemoryBandwidthEater* CAtomicMemoryBandwidthEater::NewLC(TUint32 aSize) + { + CAtomicMemoryBandwidthEater* p = new (ELeave) CAtomicMemoryBandwidthEater(aSize); + CleanupStack::PushL(p); + p->ConstructL(); + return p; + } + +CAtomicMemoryBandwidthEater::CAtomicMemoryBandwidthEater(TUint32 aSize) + : CMemoryBandwidthEater(aSize, aSize, aSize) + { + iX = TUint32(this) ^ RThread().Id().operator TUint(); + iX *= 0x9e3779b9u; + } + +CAtomicMemoryBandwidthEater::~CAtomicMemoryBandwidthEater() + { + } + +void CAtomicMemoryBandwidthEater::ConstructL() + { + CMemoryBandwidthEater::ConstructL(); + } + +TUint32 AtomicRandom(volatile TUint32* a) + { + TUint32 initial = *a; + TUint32 final; + do { + final = 69069*initial + 41; + } while(!__e32_atomic_cas_ord32(a, &initial, final)); + return final; + } + +void CAtomicMemoryBandwidthEater::Eat(TInt aTime, TUint32* aWorkDone) + { + TUint32 work = WorkValue(aTime); + if (aWorkDone) + *aWorkDone += work; + volatile TUint32* pW = (volatile TUint32*)iData; + const TUint32 mask = iSize/sizeof(TUint32)-1; + TUint32 x = AtomicRandom(&iX); + TUint32 n = work; + do { + TUint32 offset = (x>>2) & mask; + x = 69069*x+41; + __e32_atomic_add_rlx32(pW+offset, 1); + } while(--n); + } + + +/****************************************************************************** + * + ******************************************************************************/ +struct SThreadResult + { + TUint64 iElapsedTime; + TUint64 iCpuTime; + TUint32 iWorkDone; + TUint32 iInvocations; + }; + +struct SThreadParams + { + TInt64 iTestTime; + + TInt iId; + TUint32 iCpuAffinity; + + TInt iPriority; + RSemaphore iTurnstile; + + SThreadResult* iResult; + TInt iGroupId; + + MCpuEater* iE; + TUint32 iGranularity; + TUint32 iMeanIntraBurstGap; + TUint32 iMeanBurstLength; + TUint32 iMeanInterBurstGap; + }; + +class MThreadCompletion + { +public: + virtual void Complete(TBool aOk, SThreadParams* aParams)=0; + }; + +class CThreadI : public CBase + { +public: + CThreadI(); + ~CThreadI(); + static TInt ThreadFunc(TAny* aPtr); + TInt Run(); + void InitL(); +public: + CTrapCleanup* iCleanup; + CActiveScheduler* iAS; + CLoadSim* iL; + CBurstyCpuEater* iB; + CTimedStopper* iStopper; + RSemaphore iTurnstile; + SThreadParams* iParams; + }; + +CThreadI::CThreadI() + { + } + +CThreadI::~CThreadI() + { + iTurnstile.Close(); + delete iStopper; + delete iB; + delete iL; + delete iAS; + delete iCleanup; + } + +TInt CThreadI::ThreadFunc(TAny* aPtr) + { + CThreadI* p = new CThreadI; + if (!p) + return KErrNoMemory; + p->iParams = (SThreadParams*)aPtr; + return p->Run(); + } + +void CThreadI::InitL() + { + iTurnstile = iParams->iTurnstile; + User::LeaveIfError(iTurnstile.Duplicate(RThread(), EOwnerThread)); + iAS = new (ELeave) CActiveScheduler; + CActiveScheduler::Install(iAS); + iL = CLoadSim::NewL(); + CActiveScheduler::Add(iL); + const CBurstyCpuEater::SParams* params = (const CBurstyCpuEater::SParams*)&iParams->iE; + iB = new (ELeave) CBurstyCpuEater(iL, *params); + CActiveScheduler::Add(iB); + iStopper = CTimedStopper::NewL(); + CActiveScheduler::Add(iStopper); + memclr(iParams->iResult, sizeof(*iParams->iResult)); + RThread().SetPriority(TThreadPriority(iParams->iPriority)); + UserSvr::HalFunction(EHalGroupKernel, EKernelHalLockThreadToCpu, (TAny*)iParams->iCpuAffinity, 0); + } + +TInt CThreadI::Run() + { + iCleanup = CTrapCleanup::New(); + if (!iCleanup) + return KErrNoMemory; + TRAPD(r,InitL()); + if (r == KErrNone) + { + TThreadCpuUsageSample initial; + TThreadCpuUsageSample final; + RThread::Rendezvous(KErrNone); + iTurnstile.Wait(); + iB->Start(); + initial.Sample(RThread()); + iStopper->Start(iParams->iTestTime); + CActiveScheduler::Start(); + final.Sample(RThread()); + iParams->iResult->iWorkDone = iB->WorkDone(); + iParams->iResult->iInvocations = iB->Invocations(); + iParams->iResult->iElapsedTime = final.ElapsedTimeDelta(initial); + iParams->iResult->iCpuTime = final.CpuTimeDelta(initial); + } + delete this; + return r; + } + + +/****************************************************************************** + * + ******************************************************************************/ +class CThreadX : public CActive + { +public: + static CThreadX* NewL(SThreadParams* aParams, MThreadCompletion* aComplete); + static CThreadX* NewLC(SThreadParams* aParams, MThreadCompletion* aComplete); + CThreadX(); + ~CThreadX(); + void ConstructL(); + virtual void RunL(); + virtual void DoCancel(); +public: + RThread iThread; + RTimer iTimer; + SThreadParams* iParams; + MThreadCompletion* iComplete; + }; + +CThreadX::CThreadX() + : CActive(EPriorityStandard) + { + } + +CThreadX::~CThreadX() + { + Cancel(); + iTimer.Close(); + iThread.Close(); + } + +CThreadX* CThreadX::NewL(SThreadParams* aParams, MThreadCompletion* aComplete) + { + CThreadX* p = NewLC(aParams, aComplete); + CleanupStack::Pop(); + return p; + } + +CThreadX* CThreadX::NewLC(SThreadParams* aParams, MThreadCompletion* aComplete) + { + CThreadX* p = new (ELeave) CThreadX(); + p->iParams = aParams; + p->iComplete = aComplete; + CleanupStack::PushL(p); + p->ConstructL(); + return p; + } + +const TInt KThreadHeapMin = 0x1000; +const TInt KThreadHeapMax = 0x200000; +void CThreadX::ConstructL() + { + CActiveScheduler::Add(this); + TRequestStatus s0, s1; + User::LeaveIfError(iTimer.CreateLocal()); + User::LeaveIfError(iThread.Create(KNullDesC, &CThreadI::ThreadFunc, 0x1000, KThreadHeapMin, KThreadHeapMax, iParams)); + iThread.Rendezvous(s1); + if (s1!=KRequestPending) + { + User::WaitForRequest(s1); + User::Leave(s1.Int()); + } + iTimer.After(s0, 5*1000*1000); + iThread.Resume(); + User::WaitForRequest(s0, s1); + if (s1==KRequestPending) + { + iThread.Terminate(KErrCouldNotStart); + User::WaitForRequest(s1); + User::Leave(KErrTimedOut); + } + iTimer.Cancel(); + User::WaitForRequest(s0); + if (iThread.ExitType() != EExitPending) + { + User::Leave(KErrDied); + } + iThread.Logon(iStatus); + if (iStatus!=KRequestPending) + { + User::WaitForRequest(iStatus); + User::Leave(iStatus.Int()); + } + SetActive(); + User::LeaveIfError(s1.Int()); + } + +void CThreadX::DoCancel() + { + iThread.Terminate(KErrCouldNotStart); + } + +void CThreadX::RunL() + { + TBool ok = ETrue; + if (iThread.ExitType() != EExitKill) + ok = EFalse; + if (iThread.ExitReason() != KErrNone) + ok = EFalse; + if (iComplete) + iComplete->Complete(ok, iParams); + } + + +/****************************************************************************** + * + ******************************************************************************/ +struct STestThreadDesc + { + TUint32 iCpuAffinity; + TInt iPriority; + TInt iGroupId; + TUint16 iEaterType; + TUint16 iEaterInstance; + TUint32 iGranularity; + TUint32 iMeanIntraBurstGap; + TUint32 iMeanBurstLength; + TUint32 iMeanInterBurstGap; + + static STestThreadDesc* ContinuousL(TInt aPri = EPriorityNormal); + static STestThreadDesc* ContinuousLC(TInt aPri = EPriorityNormal); + static STestThreadDesc* StaccatoL(TUint32 aGranularity, TUint32 aMeanGap, TInt aPri = EPriorityNormal); + static STestThreadDesc* StaccatoLC(TUint32 aGranularity, TUint32 aMeanGap, TInt aPri = EPriorityNormal); + }; + +STestThreadDesc* STestThreadDesc::ContinuousLC(TInt aPri) + { + STestThreadDesc* p = (STestThreadDesc*)User::AllocLC(sizeof(STestThreadDesc)); + p->iCpuAffinity = 0xffffffff; + p->iPriority = aPri; + p->iGroupId = 0; + p->iEaterType = EEaterStd; + p->iEaterInstance = 0; + p->iGranularity = 0; + p->iMeanIntraBurstGap = 0; + p->iMeanBurstLength = KMaxTInt32; + p->iMeanInterBurstGap = 0; + return p; + } + +STestThreadDesc* STestThreadDesc::ContinuousL(TInt aPri) + { + STestThreadDesc* p = ContinuousLC(aPri); + CleanupStack::Pop(); + return p; + } + +STestThreadDesc* STestThreadDesc::StaccatoLC(TUint32 aGranularity, TUint32 aMeanGap, TInt aPri) + { + STestThreadDesc* p = (STestThreadDesc*)User::AllocLC(sizeof(STestThreadDesc)); + p->iCpuAffinity = 0xffffffff; + p->iPriority = aPri; + p->iGroupId = 0; + p->iEaterType = EEaterStd; + p->iEaterInstance = 0; + p->iGranularity = aGranularity; + p->iMeanIntraBurstGap = aMeanGap; + p->iMeanBurstLength = KMaxTInt32; + p->iMeanInterBurstGap = 0; + return p; + } + +STestThreadDesc* STestThreadDesc::StaccatoL(TUint32 aGranularity, TUint32 aMeanGap, TInt aPri) + { + STestThreadDesc* p = StaccatoLC(aGranularity, aMeanGap, aPri); + CleanupStack::Pop(); + return p; + } + + +class CTest : public CBase, public MThreadCompletion + { +public: + struct SStats + { + TInt64 iTotalCpu; + TInt64 iMinCpu; + TInt64 iMaxCpu; + TInt64 iTotalWork; + TInt64 iMinWork; + TInt64 iMaxWork; + }; +public: + static CTest* NewL(TInt64 aTestTime, TInt aNumTypes, ...); + ~CTest(); + RPointerArray& Threads() + { return iP; } + TInt Execute(); + void PrintResults() const; + void GetStats(SStats& aStats, TInt aFirstThread=0, TInt aCount=KMaxTInt) const; + TInt64 TotalCpuAll() const; +private: + CTest(); + void ConstructL(TInt64 aTestTime, TInt aNumTypes, VA_LIST aList); + SThreadParams* AddThreadParamsL(); + CThreadX* AddThreadXL(SThreadParams* aParams); + + virtual void Complete(TBool aOk, SThreadParams* aParams); +private: + RPointerArray iP; + RPointerArray iTX; + REaterArray iEaters; + RSemaphore iTurnstile; + TInt iCompleteCount; + TCpuUsage iInitialCpuUsage; + TCpuUsage iFinalCpuUsage; + }; + +CTest::CTest() + : iP(32), + iTX(32) + { + } + +CTest::~CTest() + { + iTX.ResetAndDestroy(); + + TInt i; + TInt c = iP.Count(); + for (i=0; iiResult); + User::Free(p); + } + } + iP.Close(); + iEaters.Close(); + iTurnstile.Close(); + } + +CTest* CTest::NewL(TInt64 aTestTime, TInt aNumTypes, ...) + { + VA_LIST list; + VA_START(list, aNumTypes); + CTest* p = new (ELeave) CTest; + CleanupStack::PushL(p); + p->ConstructL(aTestTime, aNumTypes, list); + CleanupStack::Pop(); + return p; + } + +SThreadParams* CTest::AddThreadParamsL() + { + SThreadResult* tr = (SThreadResult*)User::AllocLC(sizeof(SThreadResult)); + SThreadParams* tp = (SThreadParams*)User::AllocLC(sizeof(SThreadParams)); + memclr(tr, sizeof(SThreadResult)); + tp->iResult = tr; + iP.AppendL(tp); + CleanupStack::Pop(2); + tp->iTurnstile = iTurnstile; + return tp; + } + +CThreadX* CTest::AddThreadXL(SThreadParams* aP) + { + if (aP->iGranularity==0 && aP->iMeanInterBurstGap==0) + { + // continuous use thread + if (TInt64(aP->iMeanBurstLength) >= aP->iTestTime) + aP->iMeanBurstLength = I64LOW(aP->iTestTime) + (I64LOW(aP->iTestTime)>>1); + } + CThreadX* tx = CThreadX::NewLC(aP, this); + iTX.AppendL(tx); + CleanupStack::Pop(); + return tx; + } + +void CTest::Complete(TBool aOk, SThreadParams*) + { + if (!aOk || --iCompleteCount==0) + CActiveScheduler::Stop(); + } + + +void CTest::ConstructL(TInt64 aTestTime, TInt aNumTypes, VA_LIST aList) + { + typedef const STestThreadDesc* TTestThreadDescPtrC; + + User::LeaveIfError(iTurnstile.CreateLocal(0)); + TInt tt; + TInt tid = 0; + for (tt=0; ttiId = tid; + tp->iTestTime = aTestTime; + tp->iCpuAffinity = ttd.iCpuAffinity; + tp->iPriority = ttd.iPriority; + tp->iGroupId = ttd.iGroupId; + tp->iE = iEaters.FindOrCreateL(ttd.iEaterType, ttd.iEaterInstance); + tp->iGranularity = ttd.iGranularity; + tp->iMeanIntraBurstGap = ttd.iMeanIntraBurstGap; + tp->iMeanBurstLength = ttd.iMeanBurstLength; + tp->iMeanInterBurstGap = ttd.iMeanInterBurstGap; + AddThreadXL(tp); + } + } + } + +TInt CTest::Execute() + { + iCompleteCount = iP.Count(); + iInitialCpuUsage.Sample(); + iTurnstile.Signal(iCompleteCount); + CActiveScheduler::Start(); + iFinalCpuUsage.Sample(); + return iCompleteCount ? KErrGeneral : KErrNone; + } + +void CTest::GetStats(SStats& a, TInt aFirstThread, TInt aCount) const + { + a.iTotalCpu = 0; + a.iMinCpu = KMaxTInt64; + a.iMaxCpu = KMinTInt64; + a.iTotalWork = 0; + a.iMinWork = KMaxTInt64; + a.iMaxWork = KMinTInt64; + TInt nt = iP.Count(); + if (aFirstThread > nt) + aFirstThread = nt; + if (aCount > nt - aFirstThread) + aCount = nt - aFirstThread; + TInt i = aFirstThread; + for (; iiResult; + TInt64 cpu = tr->iCpuTime; + TInt64 work = tr->iWorkDone; + a.iTotalCpu += cpu; + a.iTotalWork += work; + if (cpu < a.iMinCpu) + a.iMinCpu = cpu; + if (cpu > a.iMaxCpu) + a.iMaxCpu = cpu; + if (work < a.iMinWork) + a.iMinWork = work; + if (work > a.iMaxWork) + a.iMaxWork = work; + } + } + +TInt64 CTest::TotalCpuAll() const + { + TInt i; + TInt nc = TCpuUsage::N(); + TInt64 totalCpuAll = 0; + for (i=0; iiResult; + test.Printf(_L("%2u: E=%10u C=%10u I=%10u W=%10u\n"), + i, I64LOW(tr->iElapsedTime), I64LOW(tr->iCpuTime), tr->iInvocations, tr->iWorkDone ); + totalCpu += tr->iCpuTime; + totalWork += TInt64(tr->iWorkDone); + } + test.Printf(_L("Total C=%12Lu W=%12Lu\n"), totalCpu, totalWork); + for (i=0; iCalibrate(); + return p; + } + case EEaterMemoryLocalS: + { + CMemoryBandwidthEater* p = CMemoryBandwidthEater::NewLC(0x8000, 0x0800, 0x0800); + p->Calibrate(); + return p; + } + case EEaterMemoryNonLocalS: + { + CMemoryBandwidthEater* p = CMemoryBandwidthEater::NewLC(0x100000, 0x10000, 0x4); + p->Calibrate(); + return p; + } + case EEaterMemoryLocalU: + { + CMemoryBandwidthEater* p = CMemoryBandwidthEater::NewLC(0x4000, 0x4000, 0x4000); + p->Calibrate(); + return p; + } + case EEaterMemoryNonLocalU: + { + CMemoryBandwidthEater* p = CMemoryBandwidthEater::NewLC(0x80000, 0x80000, 0x80000); + p->Calibrate(); + return p; + } + case EEaterMemoryAtomic: + { + CAtomicMemoryBandwidthEater* p = CAtomicMemoryBandwidthEater::NewLC(0x1000); + p->Calibrate(); + return p; + } + case EEaterMemoryAtomic2: + { + CAtomicMemoryBandwidthEater* p = CAtomicMemoryBandwidthEater::NewLC(0x8000); + p->Calibrate(); + return p; + } + case EEaterAmdahl: + { + CAmdahlCpuEater* p = CAmdahlCpuEater::NewLC(); + p->Calibrate(); + return p; + } + default: + User::Leave(KErrNotSupported); + } + return 0; + } + + + +/****************************************************************************** + * + ******************************************************************************/ + +void RunBenchmarkL(CTest::SStats& aB, STestThreadDesc* aT, TInt aLength) + { + const TInt NC = TCpuUsage::N(); + CTest* p; + TUint32 saved_aff = aT->iCpuAffinity; + aT->iCpuAffinity = NC-1; + p = CTest::NewL(aLength, 1, 1, aT); + TInt r = p->Execute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(aB); + delete p; + aT->iCpuAffinity = saved_aff; + } + +void CompareToBenchmark(const CTest::SStats& aS, const CTest::SStats& aB) + { + TReal bCpu = (TReal)aB.iTotalCpu; + TReal bWork = (TReal)aB.iTotalWork; + TReal bEff = bWork/bCpu*1000000.0; + TReal tCpu = (TReal)aS.iTotalCpu; + TReal tWork = (TReal)aS.iTotalWork; + TReal tEff = tWork/tCpu*1000000.0; + TReal mCpu = (TReal)aS.iMinCpu; + TReal MCpu = (TReal)aS.iMaxCpu; + TReal mWork = (TReal)aS.iMinWork; + TReal MWork = (TReal)aS.iMaxWork; + test.Printf(_L("Total CPU usage %6.1f%% of benchmark\n"), 100.0*tCpu/bCpu); + test.Printf(_L("Total work done %6.1f%% of benchmark\n"), 100.0*tWork/bWork); + test.Printf(_L("Max/min ratio %6.1f%% (CPU) %6.1f%% (Work)\n"), 100.0*MCpu/mCpu, 100.0*MWork/mWork); + test.Printf(_L("Work/sec bench: %10.1f test: %10.1f Relative Efficiency %6.1f%%\n"), bEff, tEff, tEff/bEff*100.0); + } + +void ContinuousTestL(TInt aLength, TUint32 aWhich) + { + aLength *= 1000; + const TInt NC = TCpuUsage::N(); + TInt r = 0; + TInt i = 0; + CTest::SStats benchmark; + CTest::SStats st; + CTest::SStats stm; + CTest* p = 0; + TUint et = aWhich >> 28; + TBool separate = (aWhich & 0x08000000u); + TInt instance = 0; + STestThreadDesc* td[2*TCpuUsage::EMaxCpus] = {0}; + STestThreadDesc* tdm[TCpuUsage::EMaxCpus] = {0}; + STestThreadDesc* tdl[TCpuUsage::EMaxCpus] = {0}; + for (i=0; i<2*NC; ++i) + { + td[i] = STestThreadDesc::ContinuousLC(); + td[i]->iEaterType = (TUint16)et; + td[i]->iEaterInstance = (TUint16)(separate ? (instance++) : 0); + if (iiEaterType = (TUint16)et; + tdm[i]->iEaterInstance = (TUint16)(separate ? (instance++) : 0); + tdl[i] = STestThreadDesc::ContinuousLC(); + tdl[i]->iCpuAffinity = i; + tdl[i]->iEaterType = (TUint16)et; + tdl[i]->iEaterInstance = (TUint16)(separate ? (instance++) : 0); + } + } + + test.Printf(_L("\nTesting a single continuous CPU-locked thread\n")); + RunBenchmarkL(benchmark, tdl[NC-1], aLength); + + TInt n; + + if (aWhich & 1) + { + for (n=1; n<=2*NC; ++n) + { + test.Printf(_L("\nTesting %d continuous thread(s) ...\n"), n); + p = CTest::NewL(aLength, 1, -n, td); + r = p->Execute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(st); + delete p; + CompareToBenchmark(st, benchmark); + } + } + + TInt h; + if (aWhich & 2) + { + for (h=1; hExecute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(st, h, l); + p->GetStats(stm, 0, h); + delete p; + CompareToBenchmark(stm, benchmark); + if (l>0) + CompareToBenchmark(st, benchmark); + } + } + } + + if (aWhich & 4) + { + for (h=1; hExecute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(st, h, l); + p->GetStats(stm, 0, h); + delete p; + CompareToBenchmark(stm, benchmark); + if (l>0) + CompareToBenchmark(st, benchmark); + } + } + } + + CleanupStack::PopAndDestroy(NC*4); + } + + +void TestWithOneSpecialL(const TDesC& aTitle, TInt aLength, const CTest::SStats& aB1, const CTest::SStats& aB0, STestThreadDesc* aT1, STestThreadDesc* aT0) + { + const TInt NC = TCpuUsage::N(); + CTest::SStats st; + CTest::SStats sti; + CTest* p = 0; + TInt n; + TInt r; + for (n=1; n<=2*NC-1; ++n) + { + test.Printf(_L("\nTesting %d continuous thread(s) plus %S ...\n"), n, &aTitle); + p = CTest::NewL(aLength, 2, 1, aT1, n, aT0); + r = p->Execute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(st, 1, n); + p->GetStats(sti, 0, 1); + delete p; + CompareToBenchmark(sti, aB1); + CompareToBenchmark(st, aB0); + + test.Printf(_L("\nTesting %d continuous thread(s) plus %Sh ...\n"), n, &aTitle); + TInt orig_pri = aT1->iPriority; + aT1->iPriority = EPriorityMore; + p = CTest::NewL(aLength, 2, 1, aT1, n, aT0); + r = p->Execute(); + test_KErrNone(r); + p->PrintResults(); + p->GetStats(st, 1, n); + p->GetStats(sti, 0, 1); + delete p; + CompareToBenchmark(sti, aB1); + CompareToBenchmark(st, aB0); + aT1->iPriority = orig_pri; + } + } + +void ContinuousPlusIntermittentTestL(TInt aLength, TUint32 aWhich) + { + aLength *= 1000; + const TInt NC = TCpuUsage::N(); + TInt i = 0; + CTest::SStats bmc, bmilc, bmilf, bmihc, bmihf; + STestThreadDesc* td = STestThreadDesc::ContinuousLC(); + STestThreadDesc* tdl[TCpuUsage::EMaxCpus] = {0}; + STestThreadDesc* tdilc[TCpuUsage::EMaxCpus] = {0}; // light load, coarse grained + STestThreadDesc* tdilf[TCpuUsage::EMaxCpus] = {0}; // light load, fine grained + STestThreadDesc* tdihc[TCpuUsage::EMaxCpus] = {0}; // heavy load, coarse grained + STestThreadDesc* tdihf[TCpuUsage::EMaxCpus] = {0}; // heavy load, fine grained + for (i=0; iiCpuAffinity = i; + tdilc[i] = STestThreadDesc::StaccatoLC(45000, 500000); + tdilf[i] = STestThreadDesc::StaccatoLC(1000, 50000); + tdihc[i] = STestThreadDesc::StaccatoLC(400000, 500000); + tdihf[i] = STestThreadDesc::StaccatoLC(3000, 5000); + } + + test.Printf(_L("\nTesting a single continuous CPU-locked thread\n")); + RunBenchmarkL(bmc, tdl[NC-1], aLength); + + if (aWhich & 1) + { + test.Printf(_L("\nTesting a single ILC CPU-locked thread\n")); + RunBenchmarkL(bmilc, tdilc[NC-1], aLength); + } + + if (aWhich & 2) + { + test.Printf(_L("\nTesting a single ILF CPU-locked thread\n")); + RunBenchmarkL(bmilf, tdilf[NC-1], aLength); + } + + if (aWhich & 4) + { + test.Printf(_L("\nTesting a single IHC CPU-locked thread\n")); + RunBenchmarkL(bmihc, tdihc[NC-1], aLength); + } + + if (aWhich & 8) + { + test.Printf(_L("\nTesting a single IHF CPU-locked thread\n")); + RunBenchmarkL(bmihf, tdihf[NC-1], aLength); + } + + if (aWhich & 1) + { + TestWithOneSpecialL(_L("ILC"), aLength, bmilc, bmc, tdilc[0], td); + } + if (aWhich & 2) + { + TestWithOneSpecialL(_L("ILF"), aLength, bmilf, bmc, tdilf[0], td); + } + if (aWhich & 4) + { + TestWithOneSpecialL(_L("IHC"), aLength, bmihc, bmc, tdihc[0], td); + } + if (aWhich & 8) + { + TestWithOneSpecialL(_L("IHF"), aLength, bmihf, bmc, tdihf[0], td); + } + CleanupStack::PopAndDestroy(5*NC+1); + } + + +TInt E32Main() + { + RThread().SetPriority(EPriorityAbsoluteHigh); + test.Title(); + User::SetCritical(User::ESystemCritical); + TCpuUsage::Init(); + TInt r = 0; + + CTrapCleanup* cln = CTrapCleanup::New(); + test_NotNull(cln); + CActiveScheduler* as = new CActiveScheduler; + test_NotNull(as); + CActiveScheduler::Install(as); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with CPU intensive loads...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4)); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4)); + test_KErrNone(r); + TRAP(r, ContinuousPlusIntermittentTestL(10000, 15)); + test_KErrNone(r); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with memory intensive loads, good locality...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4+0x08000000u+(EEaterMemoryLocalU<<28))); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4+0x08000000u+(EEaterMemoryLocalU<<28))); + test_KErrNone(r); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with memory intensive loads, poor locality...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4+0x08000000u+(EEaterMemoryNonLocalU<<28))); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4+0x08000000u+(EEaterMemoryNonLocalU<<28))); + test_KErrNone(r); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with memory intensive loads, atomic operations...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4+(EEaterMemoryAtomic<<28))); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4+(EEaterMemoryAtomic<<28))); + test_KErrNone(r); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with memory intensive loads, atomic operations 2...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4+(EEaterMemoryAtomic2<<28))); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4+(EEaterMemoryAtomic2<<28))); + test_KErrNone(r); + + test.Printf(_L("\n************************************************************************\n")); + test.Printf(_L("* Testing with CPU intensive loads with some serialization...\n")); + test.Printf(_L("************************************************************************\n")); + TRAP(r, ContinuousTestL(3000, 1+4+(EEaterAmdahl<<28))); + test_KErrNone(r); + TRAP(r, ContinuousTestL(10000, 1+4+(EEaterAmdahl<<28))); + test_KErrNone(r); + + delete as; + delete cln; + return r; + } +