// 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 <e32test.h>
#include <e32base.h>
#include <hal.h>
#include <e32atomics.h>
#include <u32hal.h>
#include <e32svr.h>
//#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; i<NumberOfCpus; ++i)
{
TFullName tname(kname);
TFullName tname2;
if (i>0)
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; ++i)
assert(IdleThread[i].GetCpuTime((TTimeIntervalMicroSeconds&)me.iIdleTime[i]) == KErrNone);
}
TInt64 TCpuUsage::ElapsedTimeDelta(const TCpuUsage& aStart) const
{
return iElapsedTime - aStart.iElapsedTime;
}
TInt64 TCpuUsage::CpuTimeDelta(const TCpuUsage& aStart, TInt aCpu) const
{
assert(TUint(aCpu) < TUint(EMaxCpus));
if (aCpu >= 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<MCpuEater>
{
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<MCpuEater>(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<<aBit);
}
CMemoryBandwidthEater* CMemoryBandwidthEater::NewLC(TUint32 aSize, TUint32 aRegionSize, TUint32 aRegionOffset)
{
CMemoryBandwidthEater* p = new (ELeave) CMemoryBandwidthEater(aSize, aRegionSize, aRegionOffset);
CleanupStack::PushL(p);
p->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)<<iNRgn);
iRegionBits = TUint8(1 + __e32_find_ms1_32(iNRgn-1));
iLowerIndexMask = ~((~0u)<<iLog2RO);
iRegionMask = (~((~0u)<<iRegionBits))<<iLog2RO;
iUpperIndexMask = ((iSize-1)>>(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<<iLog2RO);
offset |= ((aIndex<<iRegionBits) & iUpperIndexMask);
return ((TUint8*)iData) + offset;
}
TAny* CMemoryBandwidthEater::StepWithinRegion(TAny* aInitial, TUint32 aStep)
{
TUintPtr offset = TUintPtr(aInitial) - TUintPtr(iData);
TUintPtr offset2 = offset + (aStep & iLowerIndexMask);
if ((offset^offset2)&iRegionMask)
{
offset2 -= (iLowerIndexMask+1);
aStep += (iLowerIndexMask+1);
}
offset2 += ((aStep<<iRegionBits)&iUpperIndexMask);
offset2 &= (iSize-1);
return ((TUint8*)iData) + offset2;
}
void CMemoryBandwidthEater::Eat(TInt aTime, TUint32* aWorkDone)
{
TUint32 work = WorkValue(aTime);
if (aWorkDone)
*aWorkDone += work;
TInt region = AtomicAllocBit(&iRegionAlloc);
assert(region>=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<SThreadParams>& 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<SThreadParams> iP;
RPointerArray<CThreadX> 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; i<c; ++i)
{
SThreadParams* p = iP[i];
iP[i] = 0;
if (p)
{
User::Free(p->iResult);
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; tt<aNumTypes; ++tt)
{
TInt nThreads = VA_ARG(aList, TInt);
TInt inc = 0;
const TTestThreadDescPtrC* ppttd = 0;
TTestThreadDescPtrC pttd = 0;
if (nThreads < 0)
{
ppttd = VA_ARG(aList, const TTestThreadDescPtrC*);
nThreads = -nThreads;
inc = 1;
}
else
{
pttd = VA_ARG(aList, TTestThreadDescPtrC);
ppttd = &pttd;
}
TInt k;
for (k=0; k<nThreads; ++k, ++tid)
{
const STestThreadDesc& ttd = **ppttd;
ppttd += inc;
SThreadParams* tp = AddThreadParamsL();
tp->iId = 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 (; i<aFirstThread+aCount; ++i)
{
SThreadResult* tr = iP[i]->iResult;
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; i<nc; ++i)
{
TInt64 u = iFinalCpuUsage.CpuTimeDelta(iInitialCpuUsage, i);
totalCpuAll += u;
}
return totalCpuAll;
}
void CTest::PrintResults() const
{
TInt i;
TInt nt = iP.Count();
TInt nc = TCpuUsage::N();
TInt64 totalCpuAll = 0;
TInt64 totalCpu = 0;
TInt64 totalWork = 0;
for (i=0; i<nt; ++i)
{
SThreadResult* tr = iP[i]->iResult;
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; i<nc; ++i)
{
TInt64 u = iFinalCpuUsage.CpuTimeDelta(iInitialCpuUsage, i);
totalCpuAll += u;
test.Printf(_L("Cpu%1u: %10u "), i, I64LOW(u));
}
test.Printf(_L("\n"));
test.Printf(_L("Total %12Lu\n"), totalCpuAll);
}
MCpuEater* REaterArray::CreateLC(TInt aType)
{
switch (aType)
{
case EEaterStd:
{
CDefaultCpuEater* p = new (ELeave) CDefaultCpuEater();
CleanupStack::PushL(p);
p->Calibrate();
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 (i<NC)
{
tdm[i] = STestThreadDesc::ContinuousLC(EPriorityMore);
tdm[i]->iEaterType = (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; h<NC; ++h)
{
for (n=h; n<=2*NC; ++n)
{
TInt l = n - h;
test.Printf(_L("\nTesting %d continuous thread(s) (%d higher priority) CPU-locked...\n"), n, h);
p = CTest::NewL(aLength, 2, -h, tdl, l, tdl[h]);
r = p->Execute();
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; h<NC; ++h)
{
for (n=h; n<=2*NC; ++n)
{
TInt l = n - h;
test.Printf(_L("\nTesting %d continuous thread(s) (%d higher priority)...\n"), n, h);
p = CTest::NewL(aLength, 2, -h, tdm, -l, td);
r = p->Execute();
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; i<NC; ++i)
{
tdl[i] = STestThreadDesc::ContinuousLC();
tdl[i]->iCpuAffinity = 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;
}