blob: b833070f6fb09234a83c719527b38f7e3270091f [file] [log] [blame]
/* ------------------------------------------------------------------
* Copyright (C) 1998-2009 PacketVideo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied.
* See the License for the specific language governing permissions
* and limitations under the License.
* -------------------------------------------------------------------
*/
#include "oscl_scheduler.h"
#include "oscl_error.h"
#include "oscl_tickcount.h"
#include "pvlogger.h"
#include "oscl_error_trapcleanup.h"
#include "pvlogger.h"
#include "oscl_tls.h"
#include "oscl_int64_utils.h"
#define OSCL_DISABLE_WARNING_CONDITIONAL_IS_CONSTANT
#include "osclconfig_compiler_warnings.h"
#include "oscl_scheduler_tuneables.h"
/////////////////////////////////////
// Logger Macros
/////////////////////////////////////
//LOGERROR is for scheduler errors.
//This logging also goes to stderr on platforms with ANSI stdio.
#define LOGERROR(m) PVLOGGER_LOGMSG(PVLOGMSG_INST_REL,iLogger,PVLOGMSG_ERR,m);
//LOGNOTICE is for scheduler start/stop, install/uninstall notices.
#define LOGNOTICE(m) PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG,iLogger,PVLOGMSG_NOTICE,m);
#if(PV_SCHED_ENABLE_PERF_LOGGING)
//LOGSTATS is for logging the AO summary statistics. These are loggged either
//when the AO is deleted, or when the scheduling ends.
#define LOGSTATS(m) PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF,iLogger,PVLOGMSG_INFO,m);
//LOGPERF is for detailed performance logging.
#define LOGPERF_LEVEL PVLOGMSG_INFO
#define LOGPERF(m) PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF,iLogger,LOGPERF_LEVEL,m);
//LOGPERF2 is for highest detail on scheduler activity.
#define LOGPERF2_LEVEL PVLOGMSG_INFO+1
#define LOGPERF2(m) PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF,iLogger,LOGPERF2_LEVEL,m);
//max perf log string length
#define LOGPERFMAXSTR 64
#define RESET_LOG_PERF(m)\
ResetLogPerf();\
LOGPERF(m);
#else
#define LOGSTATS(m)
#define LOGPERF(m)
#define LOGPERF2(m)
#define LOGPERFMAXSTR 64
#define RESET_LOG_PERF(m)
#endif//(PV_SCHED_ENABLE_PERF_LOGGING)
/////////////////////////////////////
// DLL Entry
/////////////////////////////////////
#ifndef OSCL_COMBINED_DLL
#include "oscl_dll.h"
OSCL_DLL_ENTRY_POINT_DEFAULT()
#endif
/////////////////////////////////////
// OsclScheduler
/////////////////////////////////////
OSCL_EXPORT_REF void OsclScheduler::Init(const char *name, Oscl_DefAlloc *alloc, int nreserve)
//Init the scheduler for this thread.
{
int32 err;
OSCL_TRY(err,
OsclExecScheduler *sched = OsclExecScheduler::NewL(name, alloc, nreserve);
sched->InstallScheduler(););
if (err != OsclErrNone)
OsclError::Leave(OsclErrNotInstalled);
}
OSCL_EXPORT_REF void OsclScheduler::Cleanup()
//Cleanup the scheduler for this thread.
{
OsclExecSchedulerCommonBase *sched = OsclExecSchedulerCommonBase::GetScheduler();
if (!sched)
OsclError::Leave(OsclErrNotInstalled);
sched->UninstallScheduler();
Oscl_DefAlloc *alloc = sched->iAlloc;
sched->~OsclExecSchedulerCommonBase();
alloc->deallocate(sched);
}
/////////////////////////////////////
// OsclExecSchedulerCommonBase
/////////////////////////////////////
/////////////////////////////////////
// OsclExecScheduler
/////////////////////////////////////
//use the TLS registry, or the singleton registry if no TLS.
//Note: singleton registry only works for single-threaded
//scenarios, since this implementation assumes a per-thread registry.
#include "oscl_error.h"
#define PVSCHEDULER_REGISTRY OsclTLSRegistryEx
#define PVSCHEDULER_REGISTRY_ID OSCL_TLS_ID_PVSCHEDULER
/////////////////////////////////////
//For AO statistics.
/////////////////////////////////////
#if !(PV_SCHED_ENABLE_AO_STATS)
//no stats
#define PVTICK uint32
#define PVTICK_INT uint32
#define INIT_TICK(tick)
#define SET_TICK(tick)
#define GET_TICKFREQ(tick)
#define TICK_INT(tick1)
#define TICKSTR ""
#define TICK_INT_STR ""
#define TICK_INT_EXPR(x)
#else
//else use the oscl timer.
#define PVTICK uint32
#define PVTICK_INT uint32
#define INIT_TICK(tick) tick=0
#define SET_TICK(tick) tick=OsclTickCount::TickCount()
#define GET_TICKFREQ(tick) tick=OsclTickCount::TickCountFrequency()
#define TICK_INT(tick1) tick1
#define TICKSTR "Ticks"
#define TICK_INT_STR "%u"
#define TICK_INT_EXPR(x) x
#endif
#if !(PV_SCHED_ENABLE_AO_STATS)
#define DIFF_TICK(tick1,diff)
#define UPDATE_RUNERROR_TIME(x,y)
#define UPDATE_RUNL_TIME(x,y)
#define UPDATE_LEAVE_CODE(x,err)
#else
#define DIFF_TICK(tick1,diff) PVTICK _now;SET_TICK(_now);diff=TICK_INT(_now)-TICK_INT(tick1)
#define UPDATE_RUNERROR_TIME(stats,delta)\
if(stats->i64Valid) stats->i64TotalTicksInRun+=delta;\
else stats->iTotalTicksInRun+=delta;\
if(delta>stats->iMaxTicksInRun) stats->iMaxTicksInRun=delta;\
stats->iNumRunError++
#define UPDATE_RUNL_TIME(stats,delta)\
if(stats->i64Valid) stats->i64TotalTicksInRun+=delta;\
else stats->iTotalTicksInRun+=delta;\
if(delta>stats->iMaxTicksInRun) stats->iMaxTicksInRun=delta;\
stats->iNumRun++;
#define UPDATE_LEAVE_CODE(stats,err)if (err!=OsclErrNone)stats->iLeave=err
#endif
#if (PV_SCHED_ENABLE_LOOP_STATS)
#define DECLARE_LOOP_STATS int64 loopdelta = 0; PVTICK looptime;
#define START_LOOP_STATS(stats)SET_TICK(looptime);
#define START_WAIT_LOOP_STATS(rc,stats)\
if (rc<1)\
SET_TICK(looptime);
#define END_LOOP_STATS(stats)\
{\
DIFF_TICK(looptime,loopdelta);\
if(stats->i64Valid) stats->i64TotalTicksInRun+=loopdelta;\
else stats->iTotalTicksInRun+=Oscl_Int64_Utils::get_int64_lower32(loopdelta);\
stats->iNumRun++;\
LOGPERF((0,"PVSCHED: Run %d %s AO %s",(int32)loopdelta,TICKSTR,stats->iAOName.get_cstr()));\
}
#define END_WAIT_LOOP_STATS(rc,stats)\
if (rc<1)\
{\
DIFF_TICK(looptime,loopdelta);\
if(stats->i64Valid) stats->i64TotalTicksInRun+=loopdelta;\
else stats->iTotalTicksInRun+=Oscl_Int64_Utils::get_int64_lower32(loopdelta);\
stats->iNumRun++;\
LOGPERF((0,"PVSCHED: Run %d %s AO %s",(int32)loopdelta,TICKSTR,stats->iAOName.get_cstr()));\
}
#else
#define DECLARE_LOOP_STATS
#define START_LOOP_STATS(stats)
#define START_WAIT_LOOP_STATS(rc,stats)
#define END_LOOP_STATS(stats)
#define END_WAIT_LOOP_STATS(rc,stats)
#endif
OsclMemAllocator OsclExecSchedulerCommonBase::iDefAlloc;
#if(PV_SCHED_ENABLE_PERF_LOGGING)
void OsclExecSchedulerCommonBase::ResetLogPerf()
{
//print total time spend in prior interval of continuous Run calls.
if (iLogPerfTotal > 0)
{
LOGPERF((0, "PVSCHED: Prior Interval %d %s", iLogPerfTotal, TICKSTR));
}
//reset interval.
iLogPerfTotal = 0;
//reset indentation to zero.
iLogPerfIndentStrLen = 0;
if (iLogPerfIndentStr)
iLogPerfIndentStr[iLogPerfIndentStrLen] = '\0';
}
void OsclExecSchedulerCommonBase::IncLogPerf(uint32 delta)
{
//add to total interval time.
iLogPerfTotal += delta;
//add a space to the indent string up to the max.
if (iLogPerfIndentStr
&& iLogPerfIndentStrLen < LOGPERFMAXSTR)
{
iLogPerfIndentStr[iLogPerfIndentStrLen++] = ' ';
iLogPerfIndentStr[iLogPerfIndentStrLen] = '\0';
}
}
#endif//(PV_SCHED_ENABLE_PERF_LOGGING)
OsclExecSchedulerCommonBase* OsclExecSchedulerCommonBase::GetScheduler()
//static function to get currently installed scheduler
//for this thread.
{
OsclExecSchedulerCommonBase *current = (OsclExecSchedulerCommonBase*)PVSCHEDULER_REGISTRY::getInstance(PVSCHEDULER_REGISTRY_ID);
return current;
}
OsclExecSchedulerCommonBase* OsclExecSchedulerCommonBase::SetScheduler(OsclExecSchedulerCommonBase *a)
//static function to set currently installed scheduler
//for this thread. return previous scheduler, if any.
{
OsclExecSchedulerCommonBase* temp = GetScheduler();
PVSCHEDULER_REGISTRY::registerInstance(a, PVSCHEDULER_REGISTRY_ID);
return temp;
}
OSCL_EXPORT_REF OsclNameString<PVSCHEDNAMELEN> *OsclExecSchedulerCommonBase::GetName()
//static function to get scheduler name for this thread.
{
OsclExecSchedulerCommonBase *sched = GetScheduler();
if (sched)
return &sched->iName;
else
return NULL;
}
OSCL_EXPORT_REF uint32 OsclExecSchedulerCommonBase::GetId()
{
return PVThreadContext::Id();
}
OsclExecScheduler * OsclExecScheduler::NewL(const char *name, Oscl_DefAlloc *alloc, int nreserve)
{
OsclExecScheduler *self;
OsclMemAllocator defalloc;
OsclAny* ptr = (alloc) ? alloc->ALLOCATE(sizeof(OsclExecScheduler))
: defalloc.ALLOCATE(sizeof(OsclExecScheduler));
OsclError::LeaveIfNull(ptr);
self = OSCL_PLACEMENT_NEW(ptr, OsclExecScheduler(alloc));
OsclError::PushL(self);
self->ConstructL(name, nreserve);
OsclError::Pop();
return self;
}
OsclExecSchedulerCommonBase::~OsclExecSchedulerCommonBase()
{
//make sure scheduler is not currently installed in
//any thread.
if (IsInstalled())
OsclError::Leave(OsclErrInvalidState);//scheduler not stopped
if (iStopper)
{
iStopper->~PVSchedulerStopper();
iAlloc->deallocate(iStopper);
}
#if(PV_SCHED_ENABLE_PERF_LOGGING)
if (iLogPerfIndentStr)
_oscl_free(iLogPerfIndentStr);
#endif
}
OsclExecScheduler::~OsclExecScheduler()
{
}
OsclExecSchedulerCommonBase::OsclExecSchedulerCommonBase(Oscl_DefAlloc *alloc)
{
iAlloc = (alloc) ? alloc : &iDefAlloc;
#if(PV_SCHED_ENABLE_PERF_LOGGING)
iLogPerfIndentStr = NULL;
iLogPerfTotal = 0;
#endif
}
OsclExecScheduler::OsclExecScheduler(Oscl_DefAlloc *alloc)
: OsclExecSchedulerCommonBase(alloc)
{
}
void OsclExecSchedulerCommonBase::ConstructL(const char *name, int nreserve)
{
iNumAOAdded = 1;
OsclAny* ptr = iAlloc->ALLOCATE(sizeof(PVSchedulerStopper));
OsclError::LeaveIfNull(ptr);
iStopper = new(ptr) PVSchedulerStopper;
#if(PV_SCHED_ENABLE_AO_STATS)
ConstructStatQ();
#endif
InitExecQ(nreserve);
iBlockingMode = false;
iNativeMode = false;
iName.Set(name);
iLogger = PVLogger::GetLoggerObject("pvscheduler");
#if (PV_SCHED_ENABLE_PERF_LOGGING)
iLogPerfIndentStr = (char*)_oscl_malloc(LOGPERFMAXSTR + 1);
OsclError::LeaveIfNull(iLogPerfIndentStr);
ResetLogPerf();
#endif
}
void OsclExecScheduler::ConstructL(const char *name, int nreserve)
{
OsclExecSchedulerCommonBase::ConstructL(name, nreserve);
}
void OsclExecSchedulerCommonBase::InstallScheduler()
{
//make sure this scheduler is not installed in
//any thread.
if (IsInstalled())
OsclError::Leave(OsclErrAlreadyInstalled);
//make sure no scheduler is installed in this thread.
if (GetScheduler())
OsclError::Leave(OsclErrAlreadyInstalled);
SetScheduler(this);
iThreadContext.EnterThreadContext();
iErrorTrapImp = OsclErrorTrap::GetErrorTrapImp();
if (!iErrorTrapImp)
OsclError::Leave(OsclErrNotInstalled);//error trap not installed.
if (iStopperCrit.Create() != OsclProcStatus::SUCCESS_ERROR)
OsclError::Leave(OsclErrSystemCallFailed);//mutex error
iResumeSem.Create();
iDoStop = iDoSuspend = iSuspended = false;
iReadyQ.ThreadLogon();
LOGNOTICE((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Installed", iName.Str(), PVThreadContext::Id()));
#if (PV_SCHED_ENABLE_PERF_LOGGING)
//print tick frequencies that will show up in the perf log.
PVTICK f;
GET_TICKFREQ(f);
PVTICK_INT tickint = TICK_INT(f);
LOGPERF((0, "PVSCHED: %s frequency %s", TICKSTR, TICK_INT_STR, TICK_INT_EXPR(tickint)));
OSCL_UNUSED_ARG(tickint);
#endif
}
void OsclExecSchedulerCommonBase::UninstallScheduler()
{
//make sure this scheduler is currently installed in
//this thread.
if (!IsInstalled() || GetScheduler() != this)
OsclError::Leave(OsclErrNotInstalled);
if (iBlockingMode)
{
//in case a thread error happened, go ahead and end
//scheduling.
OsclErrorTrapImp *trap = OsclErrorTrapImp::GetErrorTrap();
if (trap
&& trap->iLeave != OsclErrNone)
EndScheduling();
//make sure scheduler is stopped. If not, leave instead.
if (IsStarted())
OsclError::Leave(OsclErrInvalidState);//scheduler not stopped
}
else if (IsStarted())
{
//end non-blocking scheduling
EndScheduling();
}
SetScheduler(NULL);
iThreadContext.ExitThreadContext();
CleanupExecQ();
//Cleanup the stat queue.
#if(PV_SCHED_ENABLE_AO_STATS)
CleanupStatQ();
#endif
if (iStopperCrit.Close() != OsclProcStatus::SUCCESS_ERROR)
OsclError::Leave(OsclErrSystemCallFailed);//mutex error
iReadyQ.ThreadLogoff();
iResumeSem.Close();
LOGNOTICE((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Uninstalled", iName.Str(), PVThreadContext::Id()));
}
OSCL_EXPORT_REF OsclExecScheduler* OsclExecScheduler::Current()
//static routine to get current scheduler.
{
return (OsclExecScheduler*)GetScheduler();
}
bool OsclExecSchedulerCommonBase::IsStarted()
{
iStopperCrit.Lock();
bool val = (iStopper->IsAdded()) ? true : false;
iStopperCrit.Unlock();
return val;
}
inline bool OsclExecSchedulerCommonBase::IsInstalled()
{
return iThreadContext.iOpen;
}
void OsclExecSchedulerCommonBase::BeginScheduling(bool blocking, bool native)
//called before entering scheduling loop.
{
//make sure scheduler is installed...
if (!IsInstalled() || GetScheduler() != this)
OsclError::Leave(OsclErrNotInstalled);
//make sure scheduler is idle...
if (IsStarted())
OsclError::Leave(OsclErrInvalidState);
iBlockingMode = blocking;
iNativeMode = native;
//Add stopper AO to scheduler.
iStopperCrit.Lock();
{
iStopper->AddToScheduler();
iStopper->PendForExec();
}
iStopperCrit.Unlock();
#if(PV_SCHED_ENABLE_PERF_LOGGING)
ResetLogPerf();
#endif
#if(PV_SCHED_ENABLE_AO_STATS)
BeginStats();
#endif
}
void OsclExecSchedulerCommonBase::EndScheduling()
//called after exiting scheduling loop.
{
//see if it's already stopped..
if (!IsStarted())
return;
//remove stopper AO.
iStopperCrit.Lock();
iStopper->RemoveFromScheduler();
iStopperCrit.Unlock();
#if(PV_SCHED_ENABLE_AO_STATS)
EndStats();
#endif
}
#if(PV_SCHED_ENABLE_AO_STATS)
void OsclExecSchedulerCommonBase::ConstructStatQ()
{
//create a placeholder for summary stats for
//all AOs that are not PVActiveBase.
for (uint32 i = 0; i < EOtherExecStats_Last; i++)
iOtherExecStats[i] = NULL;
OsclAny* ptr = iAlloc->ALLOCATE(sizeof(PVActiveStats));
OsclError::LeaveIfNull(ptr);
iOtherExecStats[EOtherExecStats_NativeOS] = OSCL_PLACEMENT_NEW(ptr, PVActiveStats(this, "Sched_TotalNativeOS", NULL));
//init the stat queue offset.
{
int offset = (int) & (iOtherExecStats[EOtherExecStats_NativeOS])->iPVStatQLink - (int)(iOtherExecStats[EOtherExecStats_NativeOS]);
iPVStatQ.SetOffset(offset);
}
#if(PV_SCHED_ENABLE_LOOP_STATS)
//create nodes for summary stats for scheduler loop time
ptr = iAlloc->ALLOCATE(sizeof(PVActiveStats));
OsclError::LeaveIfNull(ptr);
iOtherExecStats[EOtherExecStats_QueueTime] = OSCL_PLACEMENT_NEW(ptr, PVActiveStats(this, "Sched_QueueTime", NULL));
ptr = iAlloc->ALLOCATE(sizeof(PVActiveStats));
OsclError::LeaveIfNull(ptr);
iOtherExecStats[EOtherExecStats_WaitTime] = OSCL_PLACEMENT_NEW(ptr, PVActiveStats(this, "Sched_WaitTime", NULL));
#endif
//add the non-AO stats nodes to the stat Q.
{
for (uint32 i = 0; i < EOtherExecStats_Last; i++)
{
if (iOtherExecStats[i])
iPVStatQ.InsertTail(*iOtherExecStats[i]);
}
}
}
void OsclExecSchedulerCommonBase::CleanupStatQ()
{
while (!iPVStatQ.IsEmpty())
{
PVActiveStats* first = iPVStatQ.Head();
first->iPVStatQLink.Remove();
first->~PVActiveStats();
first->iScheduler->iAlloc->deallocate(first);
}
}
void OsclExecSchedulerCommonBase::ShowStats(PVActiveStats *active)
//static routine to print stats for a PV AO.
{
if (!active)
return;
//don't print any AOs that never ran.
if ((active->iNumRun + active->iNumCancel) == 0)
return;
PVLogger* iLogger = PVLogger::GetLoggerObject("pvscheduler");
if (active->i64Valid)
{
int64 avgTicksPerRun = (active->iNumRun == 0) ? 0 : active->i64TotalTicksInRun / (int64)active->iNumRun;
LOGSTATS((0, "PVSCHED:Scheduler '%s', AO '%s': Pri %d NumRun %d, MaxTicksInRun 0x%x, AvgTicksPerRun (hi,lo) (0x%x,0x%08x) Units %s, NumCancel %d, NumError %d, LeaveCode %d"
, active->iScheduler->iName.Str()
, active->iAOName.get_cstr()
, active->iPriority
, active->iNumRun
, active->iMaxTicksInRun
, Oscl_Int64_Utils::get_int64_upper32(avgTicksPerRun)
, Oscl_Int64_Utils::get_int64_lower32(avgTicksPerRun)
, TICKSTR
, active->iNumCancel
, active->iNumRunError
, active->iLeave
));
}
else
{
uint32 avgTicksPerRun = (active->iNumRun == 0) ? 0 : active->iTotalTicksInRun / active->iNumRun;
LOGSTATS((0, "PVSCHED:Scheduler '%s', AO '%s': Pri %d NumRun %d, MaxTicksInRun %d, AvgTicksPerRun %d Units %s, NumCancel %d, NumError %d, LeaveCode %d"
, active->iScheduler->iName.Str()
, active->iAOName.get_cstr()
, active->iPriority
, active->iNumRun
, active->iMaxTicksInRun
, avgTicksPerRun
, TICKSTR
, active->iNumCancel
, active->iNumRunError
, active->iLeave
));
}
}
void OsclExecSchedulerCommonBase::ShowSummaryStats(PVActiveStats *active, PVLogger*logger, int64 total, int64& aGrandTotal, float& aTotalPercent)
//static routine to print stats for a PV AO.
{
if (total == (int64)0)
return;//to avoid divide by zero
if (!active)
return;
//don't print any AO's that never ran.
if ((active->iNumRun + active->iNumCancel) == 0)
return;
//calculate percent of the total time that was spent in this AO.
if (active->i64Valid)
{
active->iPercent = 100.0 * active->i64TotalTicksInRun / total;
aGrandTotal += active->i64TotalTicksInRun;
}
else
{
active->iPercent = 100.0 * active->iTotalTicksInRun / total;
aGrandTotal += active->iTotalTicksInRun;
}
aTotalPercent += active->iPercent;
int32 fraction = (int32)active->iPercent;
float decimal = active->iPercent - fraction;
decimal *= 100.0;
//print results
if (active->i64Valid)
{
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO
, (0, " TIME PERCENT %d.%02d, AO '%s', Pri %d NumRun %d, MaxTicksInRun 0x%x, TotalTicksInRun Hi,Lo (0x%x,0x%08x), NumCancel %d, NumError %d, LeaveCode %d, NumInstance %d"
, (int32)active->iPercent, (int32)decimal
, active->iAOName.get_cstr()
, active->iPriority
, active->iNumRun
, active->iMaxTicksInRun
, Oscl_Int64_Utils::get_int64_upper32(active->i64TotalTicksInRun)
, Oscl_Int64_Utils::get_int64_lower32(active->i64TotalTicksInRun)
, active->iNumCancel
, active->iNumRunError
, active->iLeave
, active->iNumInstances
));
}
else
{
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO
, (0, " TIME PERCENT %d.%02d, AO '%s', Pri %d NumRun %d, MaxTicksInRun 0x%x, TotalTicksInRun 0x%x, NumCancel %d, NumError %d, LeaveCode %d, NumInstance %d"
, (int32)active->iPercent, (int32)decimal
, active->iAOName.get_cstr()
, active->iPriority
, active->iNumRun
, active->iMaxTicksInRun
, (int32)active->iTotalTicksInRun
, active->iNumCancel
, active->iNumRunError
, active->iLeave
, active->iNumInstances
));
}
}
void OsclExecSchedulerCommonBase::BeginStats()
//Begin stats for all AOs.
{
iTotalTicksTemp = (uint8*)OSCL_MALLOC(sizeof(PVTICK));
SET_TICK(*((PVTICK*)iTotalTicksTemp));
}
void OsclExecSchedulerCommonBase::EndStats()
//End stats for all AOs.
{
//get the end time for the scheduler run.
int64 total;
DIFF_TICK((*((PVTICK*)iTotalTicksTemp)), total);
OSCL_FREE(iTotalTicksTemp);
//there may be multiple entries per AO in the stats table, so combine them now.
if (!iPVStatQ.IsEmpty())
{
OsclDoubleRunner<PVActiveStats> iter(iPVStatQ);
PVActiveStats *item;
for (iter.SetToHead(); ; iter++)
{
item = iter;
//find all subsequent entries in the list that have
//the same AO name as this entry.
if (item->iNumInstances > 0
&& !iPVStatQ.IsTail(item))
{
OsclDoubleRunner<PVActiveStats> iter2(iPVStatQ);
PVActiveStats* item2;
for (iter2 = iter, iter2++; ; iter2++)
{
item2 = iter2;
if (item2->iAOName == item->iAOName)
{
item->Combine(*item2);
//mark this entry to ignore in further processing.
item2->iNumInstances = 0;
}
if (iPVStatQ.IsTail(item2))
break;
}
}
if (iPVStatQ.IsTail(item))
break;
}
}
//end of multiple-instance combine.
QUE_ITER_BEGIN(PVActiveStats, iPVStatQ)
{
if (item
&& item->iNumInstances > 0)
OsclExecScheduler::ShowStats(item);
}
QUE_ITER_END(iPVStatQ)
//Show summary stats
PVLogger* logger = PVLogger::GetLoggerObject("OsclSchedulerPerfStats");
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, "OSCL SCHEDULER SUMMARY STATISTICS FOR SCHEDULER '%s'", iName.Str())
)
iGrandTotalTicks = 0;
iTotalPercent = 0.0;
QUE_ITER_BEGIN(PVActiveStats, iPVStatQ)
{
if (item
&& item->iNumInstances > 0)
OsclExecScheduler::ShowSummaryStats(item, logger, total, iGrandTotalTicks, iTotalPercent);
}
QUE_ITER_END(iPVStatQ)
//split total percent into whole & decimal parts.
int32 fraction = (int32)iTotalPercent;
float decimal = iTotalPercent - fraction;
decimal *= 100.0;
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, " Total Time (hi,lo): (0x%x,0x%08x) Units: %s", Oscl_Int64_Utils::get_int64_upper32(total), Oscl_Int64_Utils::get_int64_lower32(total), TICKSTR)
)
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, " Total Time Accounted (hi,lo): (0x%x,0x%08x) Units: %s", Oscl_Int64_Utils::get_int64_upper32(iGrandTotalTicks), Oscl_Int64_Utils::get_int64_lower32(iGrandTotalTicks), TICKSTR)
)
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, " Total Percent Accounted: %d.%02d", (int32)iTotalPercent, (int32)decimal)
)
PVTICK f;
GET_TICKFREQ(f);
PVTICK_INT tickint = TICK_INT(f);
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, " Tick Units: %s Frequency: %s", TICKSTR, TICK_INT_STR, TICK_INT_EXPR(tickint))
)
PVLOGGER_LOGMSG(PVLOGMSG_INST_PROF, logger, PVLOGMSG_INFO,
(0, "END OSCL SCHEDULER SUMMARY STATISTICS FOR SCHEDULER '%s'", iName.Str())
)
}
#endif //PV_SCHED_ENABLE_AO_STATS
void OsclExecSchedulerCommonBase::Error(int32 anError) const
//call this when any AO leaves and its error handler does not handle the error.
{
LOGERROR((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Error! Reason %d", iName.Str(), PVThreadContext::Id(), anError));
fprintf(stderr, "PVSCHED:Scheduler '%s', Thread 0x%x: Error! Reason %d\n", iName.Str(), PVThreadContext::Id(), anError);
//propagate the leave
OsclError::Leave(anError);
}
OSCL_EXPORT_REF void OsclExecSchedulerCommonBase::StartScheduler(OsclSemaphore *aSignal)
//blocking call to start PV scheduler.
//Will leave if any AO leaves.
{
BeginScheduling(true, false);//blocking, non-native
if (aSignal)
aSignal->Signal();
LOGNOTICE((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Starting PV Scheduling Loop", iName.Str(), PVThreadContext::Id()));
int32 err;
OSCL_TRY(err, BlockingLoopL(););
LOGNOTICE((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Exited PV Scheduling Loop", iName.Str(), PVThreadContext::Id()));
EndScheduling();
if (err)
OsclError::Leave(err);
}
OSCL_EXPORT_REF void OsclExecSchedulerCommonBase::StartNativeScheduler()
//blocking call to start native scheduler.
//Will leave if any AO leaves.
{
OsclError::Leave(OsclErrNotSupported);//native scheduler not supported.
}
//scheduler stopper request status codes.
#define STOPPER_REQUEST_STOP_NATIVE 0
#define STOPPER_REQUEST_STOP_PV 1
#define STOPPER_REQUEST_SUSPEND 2
OSCL_EXPORT_REF void OsclExecSchedulerCommonBase::StopScheduler()
//any thread can use this to stop the blocking scheduler.
{
if (!IsInstalled())
OsclError::Leave(OsclErrNotInstalled);
if (!iBlockingMode)
OsclError::Leave(OsclErrNotReady);
if (!IsStarted())
return ;
if (iStopper->iStatus != OSCL_REQUEST_PENDING)
OsclError::Leave(OsclErrNotReady);
//in case scheduler is in the suspend loop...
if (iDoSuspend || iSuspended)
iResumeSem.Signal();
if (iNativeMode)
iStopper->PendComplete(STOPPER_REQUEST_STOP_NATIVE);
else
iStopper->PendComplete(STOPPER_REQUEST_STOP_PV);
}
OSCL_EXPORT_REF void OsclExecSchedulerCommonBase::SuspendScheduler()
//any thread can use this to suspend the blocking scheduler.
{
if (!IsInstalled())
OsclError::Leave(OsclErrNotInstalled);
if (iNativeMode)
OsclError::Leave(OsclErrNotSupported);
if (!iBlockingMode)
OsclError::Leave(OsclErrNotSupported);
if (!IsStarted())
return;
if (iStopper->iStatus != OSCL_REQUEST_PENDING)
OsclError::Leave(OsclErrNotReady);
iStopper->PendComplete(STOPPER_REQUEST_SUSPEND);
}
OSCL_EXPORT_REF void OsclExecSchedulerCommonBase::ResumeScheduler()
//any thread can use this to resume the blocking scheduler.
{
if (!IsInstalled())
OsclError::Leave(OsclErrNotInstalled);
if (iDoSuspend || iSuspended)
{
iResumeSem.Signal();
return ;
}
else
OsclError::Leave(OsclErrNotReady); //not suspended.
}
OSCL_EXPORT_REF void OsclExecScheduler::RunSchedulerNonBlocking(int32 aCount, int32 &aReady, uint32 &aShortestDelay)
//run scheduler in non-blocking mode.
//Will leave if any AO leaves.
{
aReady = 0;
aShortestDelay = 0;
//make sure this scheduler is installed.
if (!IsInstalled())
OsclError::Leave(OsclErrNotInstalled);
#if !(OSCL_RELEASE_BUILD) && !defined(NDEBUG)
//make sure this scheduler is really installed in this
//thread.
if (GetScheduler() != this)
OsclError::Leave(OsclErrNotInstalled);
#endif
//start scheduling if needed.
if (!IsStarted())
{
BeginScheduling(false, false);//nonblocking, non-native
}
else if (iBlockingMode || iNativeMode)
OsclError::Leave(OsclErrInvalidState);//not stopped
//Process timers. All ready timers will get
//moved to the ready queue.
UpdateTimersMsec(aShortestDelay);
//Run until the requested count is reached, or there
//aren't any AOs ready.
for (int32 count = 0; count < aCount;)
{
//find highest pri ready AO.
PVActiveBase* pvactive = iReadyQ.PopTop();
if (pvactive)
{
//run it
count++;
CallRunExec(pvactive);
//re-evaluate timers
UpdateTimersMsec(aShortestDelay);
}
else
break;//nothing ready
}
//at this point, either nothing else is ready or the target count was reached.
aReady = iReadyQ.Depth();
}
OSCL_EXPORT_REF void OsclExecScheduler::RegisterForCallback(OsclSchedulerObserver* aCallback, OsclAny* aCallbackContext)
{
//Update the callback pointers.
iReadyQ.RegisterForCallback(aCallback, aCallbackContext);
}
////////////////////////////////////////
// Queue Management
////////////////////////////////////////
PVActiveBase * OsclExecSchedulerCommonBase::FindPVBase(PVActiveBase *active, OsclDoubleList<PVActiveBase> &q)
//Search a PVActiveBase queue, given a PVActiveBase ptr.
{
QUE_ITER_BEGIN(PVActiveBase, q)
{
if (item == active)
return item;
}
QUE_ITER_END(q)
return NULL;
}
void OsclExecSchedulerCommonBase::CleanupExecQ()
{
//Cleanup timers.
{
PVActiveBase *top;
while ((top = iExecTimerQ.PopTop()))
top->RemoveFromScheduler();
}
//Cleanup ready AOs.
PVActiveBase* top = iReadyQ.Top();
while (top)
{
top->RemoveFromScheduler();
top = iReadyQ.Top();
}
}
void OsclExecSchedulerCommonBase::InitExecQ(int nreserve)
//init the pvactive queues.
{
iExecTimerQ.Construct(nreserve);
iReadyQ.Construct(nreserve);
}
////////////////////////////////////////
// Non-Symbian queue management
////////////////////////////////////////
void OsclExecSchedulerCommonBase::AddToExecTimerQ(PVActiveBase* anActive, uint32 aDelayMicrosec)
//timer implementation.
//Add an AO to the pending timer queue.
{
OSCL_ASSERT(anActive);//EExecNull
//make sure this AO is not already added.
if (anActive->IsInAnyQ())
OsclError::Leave(OsclErrInvalidState);//EExecAlreadyAdded
//Set time in ticks when AO should run.
uint32 tickperiod = OsclTickCount::TickCountPeriod();
OSCL_ASSERT(tickperiod != 0);
//Round to the nearest integer with the computation:
//floor((2*Interval_usec/ticks_per_usec + 1)/2)
//
//The computed time may rollover the 32-bit value-- that's OK, because
//the tick count will also rollover.
uint32 timenow = OsclTickCount::TickCount();
anActive->iPVReadyQLink.iTimeToRunTicks = timenow + (aDelayMicrosec * 2 / tickperiod + 1) / 2;
if (aDelayMicrosec > 0)
{
LOGPERF2((0, "PVSCHED:%s AO %s Timer delay %d TimeToRunTicks %d Timenow %d"
, iLogPerfIndentStr, anActive->iName.Str()
, aDelayMicrosec, anActive->iPVReadyQLink.iTimeToRunTicks
, timenow));
}
//queue it
iExecTimerQ.Add(anActive);
//if this AO is in the front of the queue now, we need to do a
//callback, because the shortest delay interval has changed.
if (iReadyQ.Callback()
&& anActive == iExecTimerQ.Top())
{
iReadyQ.TimerCallback(aDelayMicrosec);
}
}
//For 32-bit time comparisons with rollover handling.
//This value is (2^31)-1
const uint32 OsclExecSchedulerCommonBase::iTimeCompareThreshold = 0x7fffffff;
PVActiveBase* OsclExecSchedulerCommonBase::UpdateTimers(uint32 &aShortestDelay)
//timer processing.
//Complete requests for all timers that are ready now,
//then return the pending timer with the shortest delay if any.
//If any pending timer is returned it's the top of the queue so
//it can be discarded later with Pop.
{
aShortestDelay = 0;
PVActiveBase *top = iExecTimerQ.Top();
if (!top)
return NULL;
uint32 timenow = OsclTickCount::TickCount();
//Find all timers that are ready, and the first
//timer that isn't. The list is sorted by
//time then priority.
for (; top; top = iExecTimerQ.Top())
{
//calculate time to run <= timenow, taking possible rollover into account
uint32 deltaTicks = timenow - top->iPVReadyQLink.iTimeToRunTicks;
if (deltaTicks <= iTimeCompareThreshold)
{
//this timer is ready
iExecTimerQ.Pop(top);
PendComplete(top, OSCL_REQUEST_ERR_NONE, EPVThreadContext_InThread);
}
else
{
//we found the pending timer with the shortest delay.
//get the delay value
int32 delayTicks = deltaTicks;
if (delayTicks < 0)
delayTicks = (-delayTicks);
aShortestDelay = delayTicks;
return top;
}
}
return NULL;//no pending timers.
}
PVActiveBase* OsclExecSchedulerCommonBase::UpdateTimersMsec(uint32 &aShortestDelay)
//Identical to UpdateTimers except the delay returned is milliseconds instead
//of ticks.
{
aShortestDelay = 0;
PVActiveBase *top = iExecTimerQ.Top();
if (!top)
return NULL;
uint32 timenow = OsclTickCount::TickCount();
//Find all timers that are ready, and the first
//timer that isn't. The list is sorted by
//time then priority.
for (; top; top = iExecTimerQ.Top())
{
//calculate time to run <= timenow, taking possible rollover into account
uint32 deltaTicks = timenow - top->iPVReadyQLink.iTimeToRunTicks;
if (deltaTicks <= iTimeCompareThreshold)
{
//this timer is ready
iExecTimerQ.Pop(top);
PendComplete(top, OSCL_REQUEST_ERR_NONE, EPVThreadContext_InThread);
}
else
{
//we found the pending timer with the shortest delay.
//get the delay value
int32 delayTicks = deltaTicks;
if (delayTicks < 0)
delayTicks = (-delayTicks);
aShortestDelay = OsclTickCount::TicksToMsec(delayTicks);
//if delay became zero after the conversion from ticks to msec,
//then just consider this timer to be ready now.
if (aShortestDelay == 0)
{
//this timer is ready
iExecTimerQ.Pop(top);
PendComplete(top, OSCL_REQUEST_ERR_NONE, EPVThreadContext_InThread);
}
else
{
return top;
}
}
}
return NULL;//no pending timers.
}
void OsclExecSchedulerCommonBase::PendComplete(PVActiveBase *pvbase, int32 aReason, TPVThreadContext aThreadContext)
//complete a request for this scheduler.
//Calling context can be any thread.
{
//During timer cancellation, the AO may still be in the ExecTimerQ.
//Remove it now, since it won't get removed by the scheduler loop.
//Check thread context first, to accessing timer queue from out-of-thread.
if (aThreadContext == EPVThreadContext_InThread)
{
LOGPERF2((0, "PVSCHED: %s AO %s Request complete", iLogPerfIndentStr, pvbase->iName.Str()));
if (iExecTimerQ.IsIn(pvbase))
iExecTimerQ.Remove(pvbase);
}
//Pass this to the ReadyQ so it can do appropriate queue locks
int32 err = iReadyQ.PendComplete(pvbase, aReason);
OsclError::LeaveIfError(err);
}
void OsclExecSchedulerCommonBase::RequestCanceled(PVActiveBase* pvbase)
{
LOGPERF2((0, "PVSCHED: %s AO %s Request canceled", iLogPerfIndentStr, pvbase->iName.Str()));
//This gets called right after the AO's DoCancel was
//called. It must wait on the request to cancel, and remove it
//from the ready Q.
//Calling context is always in-thread, since Cancel context is always
//in-thread.
//See if the request was completed by the DoCancel.
bool complete = iReadyQ.IsIn(pvbase);
DECLARE_LOOP_STATS;
if (!complete)
{
//If request is still pending after DoCancel is called, it
//means some other thread will complete the request cancellation.
//If the AO does not have a proper DoCancel, this wait will hang up.
//reset the perf indent when scheduler gives up CPU...
RESET_LOG_PERF((0, "PVSCHED: Waiting on cancel... AO '%s'", pvbase->iName.Str()));
#if (PV_SCHED_ENABLE_PERF_LOGGING)
uint32 ticks = OsclTickCount::TickCount();
#endif
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
int32 err = iReadyQ.WaitForRequestComplete(pvbase);
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
#if (PV_SCHED_ENABLE_PERF_LOGGING)
LOGPERF((0, "PVSCHED: ...Cancel took %d Ticks", OsclTickCount::TickCount() - ticks));
#endif
OsclError::LeaveIfError(err);
}
//Set request idle and remove from ready Q.
//The AO will not run
pvbase->iBusy = false;
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
iReadyQ.Remove(pvbase);
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
}
////////////////////////////////////////
// PV Scheduling Loop Implementation
////////////////////////////////////////
void OsclExecSchedulerCommonBase::CallRunExec(PVActiveBase *pvactive)
//Run a PV AO.
{
//Set this AO inactive. This AO may be from the
//ready Q or the pending Timer Q.
//The dequeing is done by the caller.
pvactive->iBusy = false;
//start stats
#if (PV_SCHED_ENABLE_AO_STATS)
iPVStats = pvactive->iPVActiveStats;//save value now since it may change in the Run call.
iDelta = 0;
INIT_TICK(iTime);
#endif
SET_TICK(iTime);
//Call the Run under a trap harness.
//Pass the ErrorTrapImp pointer to reduce overhead of the Try call.
//We already did a null ptr check on iErrorTrapImp so it's safe to de-ref here.
int32 err;
OSCL_TRY_NO_TLS(iErrorTrapImp, err, pvactive->Run(););
//end stats
DIFF_TICK(iTime, iDelta);
UPDATE_RUNL_TIME(iPVStats, iDelta);
UPDATE_LEAVE_CODE(iPVStats, err);
#if(PV_SCHED_ENABLE_PERF_LOGGING)
//show AO time.
IncLogPerf(iDelta);
LOGPERF((0, "PVSCHED: %s Run %d %s AO %s", iLogPerfIndentStr, (int32)iDelta, TICKSTR, pvactive->iName.Str()));
#endif
//check for a leave in the Run...
if (err != OsclErrNone)
{
//start stats
SET_TICK(iTime);
//call the AO error handler
err = pvactive->RunError(err);
//end stats
DIFF_TICK(iTime, iDelta);
UPDATE_RUNERROR_TIME(iPVStats, iDelta);
//If the AO did not handle the error, indicated by returning
//ErrNone, then call the scheduler error handler.
if (err != OsclErrNone)
{
LOGERROR((0, "PVSCHED:Scheduler '%s', Thread 0x%x: Error! AO %s Error %d not handled"
, iName.Str(), PVThreadContext::Id()
, pvactive->iName.Str(), err));
fprintf(stderr, "PVSCHED:Scheduler '%s', Thread 0x%x: Error! AO %s Error %d not handled\n"
, iName.Str(), PVThreadContext::Id()
, pvactive->iName.Str(), err);
Error(err);
}
}
}
void OsclExecSchedulerCommonBase::BlockingLoopL()
//Blocking scheduling loop.
//Will leave if any AO leaves.
{
PVActiveBase* pvactive;
while (!iDoStop)
{
//Get the next AO to run. This call may block until an AO is ready.
pvactive = WaitForReadyAO();
if (pvactive)
{
CallRunExec(pvactive);
}
else
{
OsclError::Leave(OsclErrCorrupt);//EExecStrayEvent
}
//check for a suspend signal..
if (iDoSuspend)
{
iSuspended = true;
iDoSuspend = false;
iResumeSem.Wait();
iSuspended = false;
}
}//while !dostop
iDoStop = false;
}
PVActiveBase* OsclExecSchedulerCommonBase::WaitForReadyAO()
//Find the next AO to run-- non-Symbian version.
{
DECLARE_LOOP_STATS;
//First process timers.
//All ready timers will get moved to the run Q.
uint32 waitTicks;
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
PVActiveBase* pvtimer = UpdateTimers(waitTicks);
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
//Check for a ready AO.
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
PVActiveBase* pvactive = iReadyQ.PopTop();
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
if (pvactive)
{
//An AO is ready.
return pvactive;
}
else if (pvtimer)
{
//No AO is ready, but at least one timer is pending.
//Wait on shortest timer expiration or a new request.
//reset the perf logging indent each time scheduler gives up CPU.
RESET_LOG_PERF((0, "PVSCHED: Waiting on timer... Ticks %d AO %s", waitTicks, pvtimer->iName.Str()));
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
pvactive = iReadyQ.WaitAndPopTop(OsclTickCount::TicksToMsec(waitTicks));
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
if (pvactive)
{
//Another AO's request completed while we were waiting.
//Run that one instead of the timer.
return pvactive;
}
else
{
//It's time to complete this timer's request and run it.
//To save overhead, don't move it to the ReadyQ, just pop it
//from its current location on the top of the timer queue.
pvtimer->iStatus = OSCL_REQUEST_ERR_NONE;
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
iExecTimerQ.Pop(pvtimer);
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_QueueTime]);
return pvtimer;
}
}
else
{
//Nothing is ready and no timer is pending.
//Wait on a request to be completed by another thread.
//reset the perf logging indent each time scheduler gives up CPU.
RESET_LOG_PERF((0, "PVSCHED: Waiting on any request..."));
START_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
pvactive = iReadyQ.WaitAndPopTop();
END_LOOP_STATS(iOtherExecStats[EOtherExecStats_WaitTime]);
return pvactive;
}
}
////////////////////////////////////////
// PVSchedulerStopper
////////////////////////////////////////
PVSchedulerStopper::PVSchedulerStopper()
: OsclActiveObject((int32)OsclActiveObject::EPriorityHighest, "Stopper")
{
}
PVSchedulerStopper::~PVSchedulerStopper()
{
}
void PVSchedulerStopper::Run()
//This AO just waits for a signal to suspend or stop the scheduler.
{
//Stop
switch (Status())
{
case STOPPER_REQUEST_STOP_NATIVE:
break;
case STOPPER_REQUEST_STOP_PV:
{
//stop my scheduling loop
OsclExecSchedulerCommonBase* myscheduler = OsclExecScheduler::GetScheduler();
if (myscheduler)
myscheduler->iDoStop = true;
}
break;
case STOPPER_REQUEST_SUSPEND:
{
//suspend my scheduling loop
OsclExecSchedulerCommonBase* myscheduler = OsclExecScheduler::GetScheduler();
if (myscheduler)
myscheduler->iDoSuspend = true;
//re-schedule ourself
PendForExec();
}
break;
default:
break;
}
}
////////////////////////////////////////
// Symbian Coe Scheduler
////////////////////////////////////////