commands/top/topfshell.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 45 534b01198c2d
parent 0 7f656887cf89
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// topfshell.cpp
// 
// Copyright (c) 2008 - 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "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:
// Accenture - Initial contribution
//

#include <e32hashtab.h>
#include <fshell/ioutils.h>
#include <fshell/memoryaccesscmd.h>
#include "sampler.h"

using namespace IoUtils;

class CCmdTop : public CMemoryAccessCommandBase
	{
public:
	static CCommandBase* NewLC();
	~CCmdTop();
private:
	CCmdTop();
	static TInt TimerCallback(TAny* aSelf);
	void TimerCallback();
	void UpdateL();
	struct SThreadData;
	static TInt SortReverse(const CCmdTop::SThreadData& aLeft, const CCmdTop::SThreadData& aRight);

private: // From CCommandBase.
	virtual const TDesC& Name() const;
	virtual void DoRunL();
	virtual void ArgumentsL(RCommandArgumentList& aArguments);
	virtual void OptionsL(RCommandOptionList& aOptions);
private:
	RBuf8 iReadBuf;
	struct SThreadData
		{
		TUint iId;
		TInt iNumSamples;
		TFullName iName; // Saves recalculating every time
		};
	RHashMap<TUint, SThreadData*> iSeenThreads; // Saves linear searching through iThreads for each sample (Maps thread id to SThreadData
	RPointerArray<SThreadData> iThreads;
	RSampler iSampler;
	CPeriodic* iTimer;
	TInt iRate;

	CTextBuffer* iBuffer;
	CTextFormatter* iFormatter;
	TInt iNumConsoleLines;
	TInt iNumLinesInLastUpdate;
	};

CCommandBase* CCmdTop::NewLC()
	{
	CCmdTop* self = new(ELeave) CCmdTop();
	CleanupStack::PushL(self);
	self->BaseConstructL();
	return self;
	}

CCmdTop::~CCmdTop()
	{
	if (iTimer)
		{
		iTimer->Cancel();
		}
	delete iTimer;
	if (iSampler.Handle())
		{
		iSampler.Stop();
		iSampler.Close();
		User::FreeLogicalDevice(KSamplerName);
		}
	delete iBuffer;
	delete iFormatter;
	iReadBuf.Close();
	iThreads.ResetAndDestroy();
	iSeenThreads.Close();
	}

CCmdTop::CCmdTop()
	: CMemoryAccessCommandBase(EManualComplete), iRate(1000)
	{
	}

const TDesC& CCmdTop::Name() const
	{
	_LIT(KName, "top");	
	return KName;
	}

void CCmdTop::ArgumentsL(RCommandArgumentList& /*aArguments*/)
	{
	}

void CCmdTop::OptionsL(RCommandOptionList& aOptions)
	{
	aOptions.AppendIntL(iRate, _L("rate"));
	}

EXE_BOILER_PLATE(CCmdTop)

void CCmdTop::DoRunL()
	{
#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
	LoadMemoryAccessL();
#endif

	TInt err = User::LoadLogicalDevice(KSamplerName);
	if (err != KErrNone && err != KErrAlreadyExists)
		{
		LeaveIfErr(err, _L("Couldn't load sampler ldd %S"), &KSamplerName);
		}
	iReadBuf.CreateL(iRate * 2 * sizeof(TUint32)); // go twice as big as needed just to be safe, allows for AO not getting to run on time
	iTimer = CPeriodic::NewL(CActive::EPriorityStandard);
	iTimer->Start(iRate*1000, iRate*1000, TCallBack(&CCmdTop::TimerCallback, this));

	LeaveIfErr(iSampler.Open(), _L("Couldn't open sampler"));
	iSampler.Reset(ETrue); // Parameter is ignored
	iSampler.Start(1000); // Always tell sampler to sample every millisecond, regardless of the frequency we update the UI

	TSize size;
	User::LeaveIfError(Stdout().GetScreenSize(size));
	iNumConsoleLines = size.iHeight;
	iBuffer = CTextBuffer::NewL(512);
	iFormatter = CTextFormatter::NewL(size.iWidth);
	Stdout().ClearScreen();
	UpdateL(); // Just so we display something on screen before the first update
	}

TInt CCmdTop::TimerCallback(TAny* aSelf)
	{
	static_cast<CCmdTop*>(aSelf)->TimerCallback();
	return 0;
	}

void CCmdTop::TimerCallback()
	{
	iReadBuf.Zero();
	TRequestStatus stat;
	iSampler.Read(iReadBuf, stat);
	User::WaitForRequest(stat); // API used to be async, it's now sync but I haven't changed the interface
	
	// First off clear all the iNumSamples (as that was for the previous sampling period)
	for (TInt i = 0; i < iThreads.Count(); i++)
		{
		iThreads[i]->iNumSamples = 0;
		}

	TInt numSamples = iReadBuf.Length() / sizeof(TUint32);
	TUint32* buf = (TUint32*)iReadBuf.Ptr();
	for (TInt i = 0; i < numSamples; i++)
		{
		TUint32 threadId = buf[i];
		SThreadData** thread = iSeenThreads.Find(threadId);
		if (thread)
			{
			(*thread)->iNumSamples++;
			}
		else
			{
			// New thread id
			SThreadData* thread = new SThreadData;
			if (!thread) continue;
			TInt err = iThreads.Append(thread);
			if (err)
				{
				delete thread;
				continue;
				}
			err = iSeenThreads.Insert(threadId, thread);
			if (err)
				{
				iThreads.Remove(iThreads.Count()-1);
				delete thread;
				continue;
				}
			thread->iNumSamples = 1;
			thread->iId = threadId;
			RThread rthread;
#ifdef FSHELL_MEMORY_ACCESS_SUPPORT
			err = iMemAccess.RThreadForceOpen(rthread, threadId);
#else
			err = rthread.Open(TThreadId(threadId));
#endif
			if (err == KErrNone)
				{
				thread->iName = rthread.FullName();
				//PrettyName(EListThread, thread->iName);
				rthread.Close();
				}
			else
				{
				thread->iName = _L("Tid: ");
				thread->iName.AppendNum(threadId);
				}
			}
		}
	TRAP_IGNORE(UpdateL());
	}

TInt CCmdTop::SortReverse(const CCmdTop::SThreadData& aLeft, const CCmdTop::SThreadData& aRight)
	{
	// This sorts largest number of samples first 
	TInt res = - (aLeft.iNumSamples - aRight.iNumSamples);
	if (res == 0) return aLeft.iId - aRight.iId; // make sure there is a total ordering in event of tie break
	return res;
	}

void CCmdTop::UpdateL()
	{
	// First, sort iThreads
	iThreads.Sort(TLinearOrder<SThreadData>(&SortReverse));

	TInt numSamples = iReadBuf.Size() / sizeof(TUint32);
	
	iBuffer->Zero();
	iFormatter->Zero();
	iBuffer->AppendL(_L("Tid\tThread name\tCPU usage\r\n"));

	for (TInt i = 0; i < iThreads.Count() && i < iNumConsoleLines-2; i++) // minus one for title, one for last line of screen which I can't seem to figure out how to make use of
		{
		SThreadData& thread = *iThreads[i];
		TReal percent = 100 * thread.iNumSamples / (TReal)numSamples;
		iBuffer->AppendFormatL(_L("%d\t%S\t%00.2f%%\r\n"), thread.iId, &thread.iName, percent);
		}
	Stdout().SetCursorHeight(0);
	iFormatter->TabulateL(0, 1, iBuffer->Descriptor(), ETruncateLongestColumn);
	User::LeaveIfError(Stdout().SetCursorPosAbs(TPoint(0, 0)));
	Stdout().Write(iFormatter->Descriptor());
	TInt numLines = 1 + iThreads.Count(); // plus 1 for the title line
	TInt numOldLines = iNumLinesInLastUpdate - numLines;
	while (numOldLines > 0)
		{
		Stdout().ClearToEndOfLine();
		Stdout().SetCursorPosRel(TPoint(0, 1));
		--numOldLines;
		}
	iNumLinesInLastUpdate = numLines;
	}