// memsampler.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 <fshell/ioutils.h>
#include <fshell/common.mmh>
#include <e32cons.h>
#include FSHELL_D32BTRACE_HEADER
#include "memsamplerdd.h"

_LIT(KLdd, "memsamplerdd");	
const TInt KMaxCategories = 256;
const TInt KBtraceBufferSize = 64 * 1024;

using namespace IoUtils;


class TChunkInfo
	{
public:
	TChunkInfo(TUint32 aAddress);
	TChunkInfo(const TDesC& aName, TUint32 aAddress, TInt aMaxSize);
	static TBool Match(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByUpdateHistoryAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByUpdateHistoryDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByNameAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByNameDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByMaxSizeAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByMaxSizeDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByCurrentSizeAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByCurrentSizeDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByHighWaterMarkAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
	static TInt SortByHighWaterMarkDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB);
public:
	TInt iUpdateCount;
	TFullName iName;
	TUint32 iAddress;
	TInt iMaxSize;
	TInt iCurrentSize;
	TInt iHighWaterMark;
	};

TChunkInfo::TChunkInfo(TUint32 aAddress)
	: iUpdateCount(0), iAddress(aAddress), iMaxSize(0), iCurrentSize(0), iHighWaterMark(0)
	{
	}

TChunkInfo::TChunkInfo(const TDesC& aName, TUint32 aAddress, TInt aMaxSize)
	: iUpdateCount(0), iName(aName), iAddress(aAddress), iMaxSize(aMaxSize), iCurrentSize(0), iHighWaterMark(0)
	{
	}

TBool TChunkInfo::Match(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iAddress == aChunkInfoB.iAddress);
	}

TInt TChunkInfo::SortByUpdateHistoryAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iUpdateCount > aChunkInfoB.iUpdateCount);
	}

TInt TChunkInfo::SortByUpdateHistoryDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iUpdateCount < aChunkInfoB.iUpdateCount);
	}

TInt TChunkInfo::SortByNameAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iName.CompareC(aChunkInfoB.iName));
	}

TInt TChunkInfo::SortByNameDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoB.iName.CompareC(aChunkInfoA.iName));
	}

TInt TChunkInfo::SortByMaxSizeAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iMaxSize > aChunkInfoB.iMaxSize);
	}

TInt TChunkInfo::SortByMaxSizeDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iMaxSize < aChunkInfoB.iMaxSize);
	}

TInt TChunkInfo::SortByCurrentSizeAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iCurrentSize > aChunkInfoB.iCurrentSize);
	}

TInt TChunkInfo::SortByCurrentSizeDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iCurrentSize < aChunkInfoB.iCurrentSize);
	}

TInt TChunkInfo::SortByHighWaterMarkAscending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iHighWaterMark > aChunkInfoB.iHighWaterMark);
	}

TInt TChunkInfo::SortByHighWaterMarkDescending(const TChunkInfo& aChunkInfoA, const TChunkInfo& aChunkInfoB)
	{
	return (aChunkInfoA.iHighWaterMark < aChunkInfoB.iHighWaterMark);
	}


class CMemoryView : public CBase
	{
public:
	enum TSortType
		{
		ESortUnspecified,
		ESortByChunkName,
		ESortByCurrentSize,
		ESortByHighWaterMark,
		ESortByMaxSize,
		ESortByUpdateHistory
		};
	enum TSortOrder
		{
		EAscending,
		EDescending
		};
public:
	static CMemoryView* NewL(RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, TBool aHuman);
	~CMemoryView();
	void ChangeSortType(TSortType aSortType);
	void ChangeSortOrder(TSortOrder aSortOrder);
	void ScrollUp();
	void ScrollDown();
	void ScrollLeft();
	void ScrollRight();
	void HandleNewChunk(const TDesC& aName, TUint32 aAddress, TInt aMaxSize);
	void HandleChangedChunk(TUint32 aAddress, TInt aSize, TInt aHighWaterMark);
	void HandleDeletedChunk(TUint32 aAddress);
	void UpdateL();
private:
	CMemoryView(RIoWriteHandle& aStderr, TBool aHuman);
	void ConstructL(RIoWriteHandle& aStdout);
	void Sort();
	void PrintWarning(TRefByValue<const TDesC> aFmt, ...);
private:
	RIoWriteHandle& iStderr;
	TBool iHuman;
	RIoConsoleWriteHandle iStdout;
	TInt iNumConsoleLines;
	TInt iNumLinesInLastUpdate;
	RArray<TChunkInfo> iChunkList;
	CTextBuffer* iBuffer;
	CTextFormatter* iFormatter;
	TSortOrder iSortOrder;
	TLinearOrder<TChunkInfo> iSortByUpdateHistoryAscending;
	TLinearOrder<TChunkInfo> iSortByUpdateHistoryDescending;
	TLinearOrder<TChunkInfo> iSortByChunkNameAscending;
	TLinearOrder<TChunkInfo> iSortByChunkNameDescending;
	TLinearOrder<TChunkInfo> iSortByCurrentSizeAscending;
	TLinearOrder<TChunkInfo> iSortByCurrentSizeDescending;
	TLinearOrder<TChunkInfo> iSortByHighWaterMarkAscending;
	TLinearOrder<TChunkInfo> iSortByHighWaterMarkDescending;
	TLinearOrder<TChunkInfo> iSortByMaxSizeAscending;
	TLinearOrder<TChunkInfo> iSortByMaxSizeDescending;
	TLinearOrder<TChunkInfo>* iCurrentSortType;
	TInt iVerticalOffset;
	TInt iHorizontalOffset;
	};

CMemoryView* CMemoryView::NewL(RIoWriteHandle& aStdout, RIoWriteHandle& aStderr, TBool aHuman)
	{
	CMemoryView* self = new(ELeave) CMemoryView(aStderr, aHuman);
	CleanupStack::PushL(self);
	self->ConstructL(aStdout);
	CleanupStack::Pop(self);
	return self;
	}

CMemoryView::~CMemoryView()
	{
	delete iBuffer;
	delete iFormatter;
	iChunkList.Close();
	}

void CMemoryView::ChangeSortType(TSortType aSortType)
	{
	switch (aSortType)
		{
		default:
		case ESortByChunkName:
			{
			if (iCurrentSortType == &iSortByChunkNameAscending)
				{
				iCurrentSortType = &iSortByChunkNameDescending;
				}
			else if (iCurrentSortType == &iSortByChunkNameDescending)
				{
				iCurrentSortType = &iSortByChunkNameAscending;
				}
			else
				{
				iCurrentSortType = &iSortByChunkNameAscending;
				}
			break;
			}
		case ESortByCurrentSize:
			{
			if (iCurrentSortType == &iSortByCurrentSizeAscending)
				{
				iCurrentSortType = &iSortByCurrentSizeDescending;
				}
			else if (iCurrentSortType == &iSortByCurrentSizeDescending)
				{
				iCurrentSortType = &iSortByCurrentSizeAscending;
				}
			else
				{
				iCurrentSortType = &iSortByCurrentSizeDescending;
				}
			break;
			}
		case ESortByHighWaterMark:
			{
			if (iCurrentSortType == &iSortByHighWaterMarkAscending)
				{
				iCurrentSortType = &iSortByHighWaterMarkDescending;
				}
			else if (iCurrentSortType == &iSortByHighWaterMarkDescending)
				{
				iCurrentSortType = &iSortByHighWaterMarkAscending;
				}
			else
				{
				iCurrentSortType = &iSortByHighWaterMarkDescending;
				}
			break;
			}
		case ESortByMaxSize:
			{
			if (iCurrentSortType == &iSortByMaxSizeAscending)
				{
				iCurrentSortType = &iSortByMaxSizeDescending;
				}
			else if (iCurrentSortType == &iSortByMaxSizeDescending)
				{
				iCurrentSortType = &iSortByMaxSizeAscending;
				}
			else
				{
				iCurrentSortType = &iSortByMaxSizeDescending;
				}
			break;
			}
		case ESortByUpdateHistory:
			{
			if (iCurrentSortType == &iSortByUpdateHistoryAscending)
				{
				iCurrentSortType = &iSortByUpdateHistoryDescending;
				}
			else if (iCurrentSortType == &iSortByUpdateHistoryDescending)
				{
				iCurrentSortType = &iSortByUpdateHistoryAscending;
				}
			else
				{
				iCurrentSortType = &iSortByUpdateHistoryAscending;
				}
			break;
			}
		}
	Sort();
	}

void CMemoryView::ChangeSortOrder(TSortOrder aSortOrder)
	{
	iSortOrder = aSortOrder;
	}

void CMemoryView::ScrollUp()
	{
	if (iChunkList.Count() > iNumConsoleLines)
		{
		if (iVerticalOffset > 0)
			{
			// Not yet reached the top.
			--iVerticalOffset;
			}
		}
	}

void CMemoryView::ScrollDown()
	{
	const TInt numChunks = iChunkList.Count();
	if (numChunks > iNumConsoleLines)
		{
		if ((numChunks - iVerticalOffset) > iNumConsoleLines)
			{
			// Not yet reached the bottom.
			++iVerticalOffset;
			}
		}
	}

void CMemoryView::ScrollLeft()
	{
	if (iHorizontalOffset > 0)
		{
		--iHorizontalOffset;
		}
	}

void CMemoryView::ScrollRight()
	{
	// Allow the horizontal offset to increase unboundedly here - it'll be limited to something sensible in UpdateL.
	++iHorizontalOffset;
	}

void CMemoryView::HandleNewChunk(const TDesC& aName, TUint32 aAddress, TInt aMaxSize)
	{
	// Age all the existing chunk's update counts by one.
	const TInt numChunks = iChunkList.Count();
	for (TInt i = 0; i < numChunks; ++i)
		{
		++iChunkList[i].iUpdateCount;
		}

	// Insert the new chunk (claiming update count zero).
	TChunkInfo newChunkInfo(aName, aAddress, aMaxSize);
	TInt err = iChunkList.InsertInOrderAllowRepeats(newChunkInfo, *iCurrentSortType);
	if (err)
		{
		PrintWarning(_L("Couldn't handle new chunk: %d"), err);
		}
	}

void CMemoryView::HandleChangedChunk(TUint32 aAddress, TInt aSize, TInt aHighWaterMark)
	{
	TChunkInfo chunkInfo(aAddress);
	TInt pos = iChunkList.Find(chunkInfo, TChunkInfo::Match);
	if (pos < 0)
		{
		PrintWarning(_L("Couldn't handle updated to chunk 0x%08x: %d"), aAddress, pos);
		}
	else
		{
		TChunkInfo& c = iChunkList[pos];
		// Age all the chunks that have been updated more recently than this one.
		const TInt numChunks = iChunkList.Count();
		for (TInt i = 0; i < numChunks; ++i)
			{
			TChunkInfo& d = iChunkList[i];
			if (d.iUpdateCount < c.iUpdateCount)
				{
				++d.iUpdateCount;
				}
			}

		c.iUpdateCount = 0;
		c.iCurrentSize = aSize;
		c.iHighWaterMark = aHighWaterMark;
		Sort();
		}
	}

void CMemoryView::HandleDeletedChunk(TUint32 aAddress)
	{
	TChunkInfo chunkInfo(aAddress);
	TInt pos = iChunkList.Find(chunkInfo, TChunkInfo::Match);
	if (pos < 0)
		{
		PrintWarning(_L("Couldn't handle deletion of chunk 0x%08x: %d"), aAddress, pos);
		}
	else
		{
		TChunkInfo& c = iChunkList[pos];

		// Rejuvenate chunks that haven't been updated since this one last was.
		const TInt numChunks = iChunkList.Count();
		for (TInt i = 0; i < numChunks; ++i)
			{
			TChunkInfo& d = iChunkList[i];
			if (d.iUpdateCount > c.iUpdateCount)
				{
				--d.iUpdateCount;
				}
			}
		
		iChunkList.Remove(pos);
		if (iChunkList.Count() <= iNumConsoleLines)
			{
			iVerticalOffset = 0;
			}
		}
	}

CMemoryView::CMemoryView(RIoWriteHandle& aStderr, TBool aHuman)
	: iStderr(aStderr),
	iHuman(aHuman),
	iSortByUpdateHistoryAscending(TChunkInfo::SortByUpdateHistoryAscending),
	iSortByUpdateHistoryDescending(TChunkInfo::SortByUpdateHistoryDescending),
	iSortByChunkNameAscending(TChunkInfo::SortByNameAscending),
	iSortByChunkNameDescending(TChunkInfo::SortByNameDescending),
	iSortByCurrentSizeAscending(TChunkInfo::SortByCurrentSizeAscending),
	iSortByCurrentSizeDescending(TChunkInfo::SortByCurrentSizeDescending),
	iSortByHighWaterMarkAscending(TChunkInfo::SortByHighWaterMarkAscending),
	iSortByHighWaterMarkDescending(TChunkInfo::SortByHighWaterMarkDescending),
	iSortByMaxSizeAscending(TChunkInfo::SortByMaxSizeAscending),
	iSortByMaxSizeDescending(TChunkInfo::SortByMaxSizeDescending),
	iCurrentSortType(&iSortByUpdateHistoryAscending)
	{
	}

void CMemoryView::ConstructL(RIoWriteHandle& aStdout)
	{
	if (aStdout.AttachedToConsole())
		{
		iStdout = aStdout;
		TSize size;
		User::LeaveIfError(iStdout.GetScreenSize(size));
		iNumConsoleLines = size.iHeight;
		iBuffer = CTextBuffer::NewL(0x100);
		iFormatter = CTextFormatter::NewL(size.iWidth);
		iStdout.SetCursorHeight(0);
		iStdout.ClearScreen();
		}
	else
		{
		PrintWarning(_L("memsampler can't run unless it is attached directly to a console, aborting..."));
		User::Leave(KErrArgument);
		}

	}

void CMemoryView::Sort()
	{
	iChunkList.Sort(*iCurrentSortType);
	}

void CMemoryView::UpdateL()
	{
	iBuffer->Zero();
	iFormatter->Zero();
	const TInt numChunks = iChunkList.Count();
	iBuffer->AppendL(_L("Chunk name\t   Current\t       Max\t      Peak\r\n"));
	TInt numLines = 1;
	for (TInt i = iVerticalOffset; i < numChunks; ++i)
		{
		++numLines;
		const TChunkInfo& chunkInfo = iChunkList[i];
		if (iHuman)
			{
			if (iHorizontalOffset >= chunkInfo.iName.Length())
				{
				// The horizontal offset is larger than this chunk name - reduce it to stop the chunk name column disappearing altogether.
				// Note, ideally it would be nice to limit horizontal scrolling when the right most part of all the chunk names is visible.
				// However, that would involve calculating all the column widths twice which seems a bit clumsy.
				iHorizontalOffset = chunkInfo.iName.Length() - 1;
				}
			TPtrC name(chunkInfo.iName.Mid(iHorizontalOffset));
			iBuffer->AppendFormatL(_L("%S\t"), &name);
			iBuffer->AppendHumanReadableSizeL(chunkInfo.iCurrentSize, EColumnAlignedRight);
			iBuffer->AppendL(_L("\t"));
			iBuffer->AppendHumanReadableSizeL(chunkInfo.iMaxSize, EColumnAlignedRight);
			iBuffer->AppendL(_L("\t"));
			iBuffer->AppendHumanReadableSizeL(chunkInfo.iHighWaterMark, EColumnAlignedRight);
			iBuffer->AppendL(_L("\r\n"));
			}
		else
			{
			iBuffer->AppendFormatL(_L("%S\t%d\t%d\t%d\r\n"), &chunkInfo.iName, chunkInfo.iCurrentSize, chunkInfo.iMaxSize, chunkInfo.iHighWaterMark);
			}
		if (numLines >= (iNumConsoleLines - 1))
			{
			break;
			}
		}
	iFormatter->TabulateL(0, 1, iBuffer->Descriptor(), ETruncateLongestColumn);
	User::LeaveIfError(iStdout.SetCursorPosAbs(TPoint(0, 0)));
	iStdout.Write(iFormatter->Descriptor());
	TInt numOldLines = iNumLinesInLastUpdate - numLines;
	while (numOldLines > 0)
		{
		iStdout.ClearToEndOfLine();
		iStdout.SetCursorPosRel(TPoint(0, 1));
		--numOldLines;
		}
	iNumLinesInLastUpdate = numLines;
	}

void CMemoryView::PrintWarning(TRefByValue<const TDesC> aFmt, ...)
	{
	TOverflowTruncate overflow;
	VA_LIST list;
	VA_START(list, aFmt);
	TBuf<0x100> buf(_L("Warning: "));
	buf.AppendFormatList(aFmt, list, &overflow);
	buf.AppendFormat(_L("\r\n"), &overflow);
	iStderr.Write(buf);
	}


class CStdinReader : public CActive
	{
public:
	static CStdinReader* NewL(RIoReadHandle& aStdin, CCommandBase& aCommand, CMemoryView* aMemoryView);
	~CStdinReader();
private: // From CActive.
	virtual void RunL();
	virtual void DoCancel();
	virtual TInt RunError(TInt aError);
private:
	CStdinReader(RIoReadHandle& aStdin, CCommandBase& aCommand, CMemoryView* aMemoryView);
	void QueueRead();
private:
	RIoConsoleReadHandle iStdin;
	CCommandBase& iCommand;
	CMemoryView* iMemoryView;
	};

CStdinReader* CStdinReader::NewL(RIoReadHandle& aStdin, CCommandBase& aCommand, CMemoryView* aMemoryView)
	{
	CStdinReader* self = new(ELeave) CStdinReader(aStdin, aCommand, aMemoryView);
	self->QueueRead();
	return self;
	}

CStdinReader::~CStdinReader()
	{
	Cancel();
	}

void CStdinReader::RunL()
	{
	if (iStatus.Int())
		{
		iCommand.Complete(iStatus.Int());
		}
	else
		{
		TBool noUpdate(EFalse);
		CMemoryView::TSortType newSortType = CMemoryView::ESortUnspecified;
		switch (iStdin.KeyCode())
			{
			case 'n':
			case 'N':
				{
				newSortType = CMemoryView::ESortByChunkName;
				break;
				}
			case 'c':
			case 'C':
				{
				newSortType = CMemoryView::ESortByCurrentSize;
				break;
				}
			case 'p':
			case 'P':
				{
				newSortType = CMemoryView::ESortByHighWaterMark;
				break;
				}
			case 'm':
			case 'M':
				{
				newSortType = CMemoryView::ESortByMaxSize;
				break;
				}
			case 'u':
			case 'U':
				{
				newSortType = CMemoryView::ESortByUpdateHistory;
				break;
				}
			case 'q':
			case 'Q':
				{
				iCommand.Complete(KErrNone);
				break;
				}
			case EKeyUpArrow:
				{
				if (iMemoryView)
					{
					iMemoryView->ScrollUp();
					}
				break;
				}
			case EKeyDownArrow:
				{
				if (iMemoryView)
					{
					iMemoryView->ScrollDown();
					}
				break;
				}
			case EKeyLeftArrow:
				{
				if (iMemoryView)
					{
					iMemoryView->ScrollLeft();
					}
				break;
				}
			case EKeyRightArrow:
				{
				if (iMemoryView)
					{
					iMemoryView->ScrollRight();
					}
				break;
				}
			default:
				{
				noUpdate = ETrue;
				}
			}

		if ((newSortType != CMemoryView::ESortUnspecified) && iMemoryView)
			{
			iMemoryView->ChangeSortType(newSortType);
			}

		if (iMemoryView && !noUpdate)
			{
			iMemoryView->UpdateL();
			}

		QueueRead();
		}
	}

void CStdinReader::DoCancel()
	{
	iStdin.WaitForKeyCancel();
	}

TInt CStdinReader::RunError(TInt aError)
	{
	iCommand.Complete(aError);
	return KErrNone;
	}

CStdinReader::CStdinReader(RIoReadHandle& aStdin, CCommandBase& aCommand, CMemoryView* aMemoryView)
	: CActive(CActive::EPriorityStandard), iCommand(aCommand), iMemoryView(aMemoryView)
	{
	iStdin = aStdin;
	CActiveScheduler::Add(this);
	}

void CStdinReader::QueueRead()
	{
	iStdin.WaitForKey(iStatus);
	SetActive();
	}



class TBtraceHeader
	{
public:
	TUint8 iSize;
	TUint8 iFlags;
	TUint8 iCategory;
	TUint8 iSubCategory;
	};

class CBtraceReader : public CActive
	{
public:
	enum TMode
		{
		EConfigBtrace = 0x0001,
		EDebug        = 0x0002
		};
public:
	static CBtraceReader* NewL(TUint aMode, const TDesC& aFileName, CCommandBase& aCommand, CMemoryView* aMemoryView);
	~CBtraceReader();
private: // From CActive.
	virtual void RunL();
	virtual void DoCancel();
	virtual TInt RunError(TInt aError);
private:
	CBtraceReader(const TDesC& aFileName, CCommandBase& aCommand, CMemoryView* aMemoryView);
	void ConstructL(TUint aMode);
	void QueueRead();
	void DecodeFrame(const TBtraceHeader& aHeader, const TDesC8& aFrame, TUint32 aTickCount);
	void Printf(TRefByValue<const TDesC> aFmt, ...);
private:
	const TDesC& iFileName;
	CCommandBase& iCommand;
	CMemoryView* iMemoryView;
	RBTrace iBtrace;
	CConsoleBase* iDebugConsole;
	RFile iFile;
	};

CBtraceReader* CBtraceReader::NewL(TUint aMode, const TDesC& aFileName, CCommandBase& aCommand, CMemoryView* aMemoryView)
	{
	CBtraceReader* self = new(ELeave) CBtraceReader(aFileName, aCommand, aMemoryView);
	CleanupStack::PushL(self);
	self->ConstructL(aMode);
	CleanupStack::Pop(self);
	return self;
	}

CBtraceReader::~CBtraceReader()
	{
	Cancel();
	iBtrace.Close();
	iFile.Close();
	delete iDebugConsole;
	}

void CBtraceReader::RunL()
	{
	QueueRead();
	TUint8* data;
	TInt size;
	while ((size = iBtrace.GetData(data)) != 0)
		{
		if (iMemoryView || iDebugConsole)
			{
			// Only decode btrace frames if we're attached to a CMemoryView object or have a debug console.
			TUint8* c = data;
			TUint8* end = c + size;
			do
				{
				TBtraceHeader* header = (TBtraceHeader*)c;
				TUint8* d = c + sizeof(TBtraceHeader);
				TUint32 tickCount = 0;
				if (header->iFlags & BTrace::EMissingRecord)
					{
					User::Leave(KErrOverflow);
					}
				if (header->iFlags & BTrace::EHeader2Present)
					{
					d += 4;
					}
				if (header->iFlags & BTrace::ETimestampPresent)
					{
					tickCount = *((TUint32*)d);
					d += 4;
					}
				if (header->iFlags & BTrace::ETimestamp2Present)
					{
					d += 4;
					}
				if (header->iFlags & BTrace::EContextIdPresent)
					{
					d += 4;
					}
				if (header->iFlags & BTrace::EPcPresent)
					{
					d += 4;
					}
				if (header->iFlags & BTrace::EExtraPresent)
					{
					d += 4;
					}
				TPtrC8 ptr(d, (c + header->iSize) - d);
				DecodeFrame(*header, ptr, tickCount);
				c += (header->iSize + 3) & ~3;
				}
				while (c < end);
			}
		if (iFileName.Length())
			{
			User::LeaveIfError(iFile.Write(TPtrC8(data, size)));
			}
		iBtrace.DataUsed();
		}
	if (iMemoryView)
		{
		iMemoryView->UpdateL();
		}
	}

void CBtraceReader::DoCancel()
	{
	iBtrace.CancelRequestData();
	}

TInt CBtraceReader::RunError(TInt aError)
	{
	if (aError == KErrOverflow)
		{
		Printf(_L("Warning: BTrace buffer overflowed, aborting..."), aError);
		iCommand.Complete(aError);
		}
	else if (aError)
		{
		Printf(_L("Warning: Could not update view (%d), aborting..."), aError);
		iCommand.Complete(aError);
		}
	return KErrNone;
	}

CBtraceReader::CBtraceReader(const TDesC& aFileName, CCommandBase& aCommand, CMemoryView* aMemoryView)
	: CActive(CActive::EPriorityStandard), iFileName(aFileName), iCommand(aCommand), iMemoryView(aMemoryView)
	{
	CActiveScheduler::Add(this);
	}

void CBtraceReader::ConstructL(TUint aMode)
	{
	if (aMode & EDebug)
		{
		iDebugConsole = Console::NewL(_L("debug"), TSize(KConsFullScreen,KConsFullScreen));
		}
	User::LeaveIfError(iBtrace.Open());
	if (aMode & EConfigBtrace)
		{
		User::LeaveIfError(iBtrace.ResizeBuffer(KBtraceBufferSize));
		// Turn everything off.
		for (TInt i = 0; i < KMaxCategories; ++i)
			{
			iBtrace.SetFilter(i, 0);
			}
		if (aMode & EDebug)
			{
			iBtrace.SetFilter(BTrace::EKernPrintf, 1);
			}
		iBtrace.SetFilter(RMemSampler::EBtraceCategory, 1);
		User::LeaveIfError(iBtrace.SetFilter2((const TUint32*)NULL, 0));
		iBtrace.SetMode(RBTrace::EEnable | RBTrace::EFreeRunning);
		}
	if (iFileName.Length())
		{
		User::LeaveIfError(iFile.Replace(iCommand.FsL(), iFileName, EFileWrite));
		}
	QueueRead();
	}

void CBtraceReader::QueueRead()
	{
	iBtrace.RequestData(iStatus, 0);
	SetActive();
	}

void CBtraceReader::DecodeFrame(const TBtraceHeader& aHeader, const TDesC8& aFrame, TUint32)
	{
	if (aHeader.iCategory == BTrace::EKernPrintf)
		{
		TUint32 threadId = *(TUint32*)aFrame.Ptr();
		TBuf<256> text;
		text.Copy(aFrame.Mid(4));
		if (iDebugConsole)
			{
			iDebugConsole->Printf(_L("Kern::Printf (0x%08x) \'%S\'\r\n"), &threadId, &text);
			}
		}
	else if (aHeader.iCategory == RMemSampler::EBtraceCategory)
		{
		switch (aHeader.iSubCategory)
			{
			case RMemSampler::ENewChunk:
				{
				TUint32 address = *(TUint32*)aFrame.Ptr();
				TInt maxSize = *((TUint32*)aFrame.Ptr() + 1);
				TFullName fullName;
				fullName.Copy(aFrame.Mid(8));
				if (iDebugConsole)
					{
					iDebugConsole->Printf(_L("New chunk - %S\r\n\taddress: 0x%08x max size: %d\r\n"), &fullName, address, maxSize);
					}
				if (iMemoryView)
					{
					iMemoryView->HandleNewChunk(fullName, address, maxSize);
					}
				break;
				}
			case RMemSampler::EChangedChunk:
				{
				TUint32 address = *(TUint32*)aFrame.Ptr();
				TUint32 size = *((TUint32*)aFrame.Ptr() + 1);
				TUint32 highWaterMark = *((TUint32*)aFrame.Ptr() + 2);
				if (iDebugConsole)
					{
					iDebugConsole->Printf(_L("Changed chunk - address: 0x%08x size: %d hwm: %d\r\n"), address, size, highWaterMark);
					}
				if (iMemoryView)
					{
					iMemoryView->HandleChangedChunk(address, size, highWaterMark);
					}
				break;
				}
			case RMemSampler::EDeletedChunk:
				{
				TUint32 address = *(TUint32*)aFrame.Ptr();
				if (iDebugConsole)
					{
					iDebugConsole->Printf(_L("Deleted chunk - address: 0x%08x\r\n"), address);
					}
				if (iMemoryView)
					{
					iMemoryView->HandleDeletedChunk(address);
					}
				break;
				}
			}
		}
	}

void CBtraceReader::Printf(TRefByValue<const TDesC> aFmt, ...)
	{
	TOverflowTruncate overflow;
	VA_LIST list;
	VA_START(list, aFmt);
	TBuf<0x100> buf;
	buf.AppendFormatList(aFmt, list, &overflow);
	iCommand.Stdout().Write(buf);
	}


class CCmdMemsampler : public CCommandBase
	{
public:
	static CCommandBase* NewLC();
	~CCmdMemsampler();
private:
	CCmdMemsampler();
private: // From CCommandBase.
	virtual const TDesC& Name() const;
	virtual void DoRunL();
	virtual void OptionsL(RCommandOptionList& aOptions);
private:
	RMemSampler iMemSampler;
	CMemoryView* iMemoryView;
	CStdinReader* iStdinReader;
	CBtraceReader* iBtraceReader;
	TUint iRate;
	TBool iNoBtraceConfig;
	TBool iDebug;
	TBool iHuman;
	TFileName2 iFileName;
	TBool iNoLiveView;
	};


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

CCmdMemsampler::~CCmdMemsampler()
	{
	iMemSampler.Close();
	User::FreeLogicalDevice(KLdd);
	delete iStdinReader;
	delete iBtraceReader;
	delete iMemoryView;
	}

CCmdMemsampler::CCmdMemsampler() : CCommandBase(EManualComplete), iRate(1000)
	{
	}

const TDesC& CCmdMemsampler::Name() const
	{
	_LIT(KName, "memsampler");	
	return KName;
	}

void CCmdMemsampler::DoRunL()
	{
	TUint mode = 0;
	if (!iNoBtraceConfig)
		{
		mode |= CBtraceReader::EConfigBtrace;
		}
	if (iDebug)
		{
		mode |= CBtraceReader::EDebug;
		}

	if (!iNoLiveView)
		{
		iMemoryView = CMemoryView::NewL(Stdout(), Stderr(), iHuman);
		}
	iStdinReader = CStdinReader::NewL(Stdin(), *this, iMemoryView);
	iBtraceReader = CBtraceReader::NewL(mode, iFileName, *this, iMemoryView);
	User::LeaveIfError(User::LoadLogicalDevice(KLdd));
	User::LeaveIfError(iMemSampler.Open());
	iMemSampler.Start(iRate);
	}

void CCmdMemsampler::OptionsL(RCommandOptionList& aOptions)
	{
	_LIT(KOptSampleRate, "rate");
	aOptions.AppendUintL(iRate, KOptSampleRate);

	_LIT(KOptNoBtraceConfig, "no-btrace-config");
	aOptions.AppendBoolL(iNoBtraceConfig, KOptNoBtraceConfig);

	_LIT(KOptDebug, "debug");
	aOptions.AppendBoolL(iDebug, KOptDebug);

	_LIT(KOptHuman, "human");
	aOptions.AppendBoolL(iHuman, KOptHuman);

	_LIT(KOptFile, "file");
	aOptions.AppendFileNameL(iFileName, KOptFile);

	_LIT(KOptNoLiveView, "no-live-view");
	aOptions.AppendBoolL(iNoLiveView, KOptNoLiveView);
	}


EXE_BOILER_PLATE(CCmdMemsampler)

