// Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the License "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// Other possible inputs to thrashing detection:
// - cache size and free ram
// - pin failures
//



#include <kernel/kern_priv.h>
#include "mthrash.h"

const TInt KUpdatePeriod = 1000;   // Update every second

DThrashMonitor TheThrashMonitor;

DThrashMonitor::DThrashMonitor() :
	iRunning(EFalse),
	iUpdateTimer(NULL, this),
	iUpdateDfc(UpdateDfcFunc, this, 0),
	iThrashLevel(0),
	iThresholdThrashing(200),
	iThresholdGood(150)
	{
	}

void DThrashMonitor::Start()
	{
	TBool alreadyRunning = __e32_atomic_swp_ord32(&iRunning, ETrue);
	if (alreadyRunning)
		return;
	
	// reset
	memclr(&iCount[0], sizeof(iCount));
	iLastUpdateTime = NKern::TickCount();
	
	iUpdateDfc.SetDfcQ(Kern::DfcQue0());
	TInt r = iUpdateTimer.OneShot(KUpdatePeriod, iUpdateDfc);
	__NK_ASSERT_ALWAYS(r == KErrNone);
	}

TInt DThrashMonitor::ThrashLevel()
	{
	return iThrashLevel;
	}

TInt DThrashMonitor::SetThresholds(TUint aThrashing, TUint aGood)
	{
	if (aThrashing < aGood || aThrashing > 255)
		return KErrArgument;
	iThresholdThrashing = aThrashing;
	iThresholdGood = aGood;
	return KErrNone;
	}

void DThrashMonitor::UpdateCount(TCount aCount, TInt aDelta)
	{
	TCountData& c = iCount[aCount];

	NKern::FMWait(&iMutex);
	
	TUint32 currentTime = NKern::TickCount();
	c.iTotal += (TInt64)c.iCount * (currentTime - c.iLastUpdateTime);
	c.iCount += aDelta;
	c.iLastUpdateTime = currentTime;
	
	NKern::FMSignal(&iMutex);
	
	__NK_ASSERT_DEBUG(c.iCount >= 0);
	}

void DThrashMonitor::NotifyStartPaging()
	{
	UpdateCount(ECountThreadsPaging, 1);
	}

void DThrashMonitor::NotifyEndPaging()
	{
	UpdateCount(ECountThreadsPaging, -1);
	}

void DThrashMonitor::UpdateDfcFunc(TAny* aPtr)
	{
	DThrashMonitor* self = (DThrashMonitor*)aPtr;
	self->RecalculateThrashLevel();
	}

void DThrashMonitor::RecalculateThrashLevel()
	{
	TInt currentTime = NKern::TickCount();
	TInt elapsedTicks = currentTime - iLastUpdateTime;

	NKern::FMWait(&iMutex);
	for (TInt i = 0 ; i < EMaxCount ; ++i)
		{
		TCountData& c = iCount[i];
		c.iTotal += (TInt64)c.iCount * (currentTime - c.iLastUpdateTime);
		c.iAverage = (TInt)((256 * c.iTotal) / elapsedTicks);
		c.iTotal = 0;
		c.iLastUpdateTime = currentTime;
		}
	NKern::FMSignal(&iMutex);

	TInt pagingActivity = Min(iCount[ECountThreadsPaging].iAverage, 255);

	// Base thrash level entirely on the average number of threads paging
	TInt newThrashLevel = pagingActivity;
	TInt oldThrashLevel = iThrashLevel;

	// Make thrash level increase slowly over time, but decrease quickly
	if (newThrashLevel > oldThrashLevel)
		newThrashLevel = (3 * oldThrashLevel + pagingActivity) >> 2;

	iThrashLevel = newThrashLevel;
	
	// Notify user-side if thrashing thresholds passed
	TBool notifyChange = EFalse;
	if (oldThrashLevel < iThresholdThrashing && newThrashLevel >= iThresholdThrashing)
		{
		iIsThrashing = ETrue;
		notifyChange = ETrue;
		}
	else if (iIsThrashing && oldThrashLevel >= iThresholdGood && newThrashLevel < iThresholdGood)
		{
		iIsThrashing = EFalse;
		notifyChange = ETrue;
		}
	
	if (notifyChange)
		{
		NKern::ThreadEnterCS();
		Kern::AsyncNotifyChanges(EChangesThrashLevel);
		NKern::ThreadLeaveCS();
		}
	
	iLastUpdateTime = currentTime;
	TInt r = iUpdateTimer.Again(KUpdatePeriod);
	if (r == KErrArgument)
		r = iUpdateTimer.OneShot(KUpdatePeriod, iUpdateDfc);
	__NK_ASSERT_ALWAYS(r == KErrNone);
	}
