diff -r 000000000000 -r a41df078684a kerneltest/e32utils/profiler/profiler.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/kerneltest/e32utils/profiler/profiler.cpp Mon Oct 19 15:55:17 2009 +0100 @@ -0,0 +1,1018 @@ +// Copyright (c) 1998-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: +// e32utils\profiler\profiler.cpp +// +// + +#include +#include +#include "profiler.h" +#include "sampler.h" + +// The name of the output file use to save the sample data +_LIT(KFileName,"?:\\PROFILER.DAT"); +const TInt KFileNameLen=15; + +// The name of the DLL used as an alternative UI controller +_LIT(KProfilerKeysDll,"ProfilerKeys"); + +// The size of the buffers used for reading sample data and writing to file +const TInt KBufferSize = 0x800; + +// The sample rate used by the profiler +const TInt KSampleRate = 1000; + +const TInt KCommandMask = 0x00ff; +const TInt KCommandNone = 0x0010; +const TInt KCommandNoUi = 0x0100; +const TInt KCommandXIPOnly = 0x0200; + +// The controller class used to provide the Profiler functions. +// This runs as a server in the engine thread +class CPServer : public CServer2, public MProfilerController + { +public: + static MProfilerController* NewL(TInt aPriority, MProfilerEngine& aEngine); +private: + CPServer(TInt aPriority, MProfilerEngine& aEngine); + void Release(); + CSession2* NewSessionL(const TVersion& aVersion,const RMessage2& aMessage) const; + }; + +// The session class used by the server controller +class CPSession : public CSession2 + { +private: + inline const CPServer& Server() const; + void ServiceL(const RMessage2& aMessage); + }; + + +// The default UI controller class which uses a Console +class CConsole : public CActive, private MProfilerController + { +public: + static MProfilerController* NewL(TInt aPriority, MProfilerEngine& aEngine); +private: + CConsole(TInt aPriority, MProfilerEngine& aEngine); + void ConstructL(); + ~CConsole(); + void Release(); +// + void Help(); + void Queue(); +// + void RunL(); + void DoCancel(); +private: + CConsoleBase* iConsole; + }; + + +// The buffers used for transferring data from the device driver to the file +struct TBuffer + { + TBuffer* iNext; + TBuf8 iBuf; + }; + +class CProfiler; + +// The active object responsible for reading data from the device +class CReader : public CActive + { +public: + CReader(TInt aPriority, CProfiler& aProfiler); + ~CReader(); +// + void ConstructL(); + void Queue(TBuffer* aBuf); +private: + void RunL(); + void DoCancel(); +private: + CProfiler& iProfiler; + TBuffer* iBuf; +public: + RSampler iSampler; + }; + +// The active object responsible for writing data out (to file) +class CWriter : public CActive + { +public: + CWriter(TInt aPriority, CProfiler& aProfiler); + ~CWriter(); + void ConstructL(); + TInt Open(const TDesC& aFile); + void Close(); + void Queue(TBuffer* aBuf); +private: + void RunL(); + void DoCancel(); +private: + CProfiler& iProfiler; + TBuffer* iBuf; + RFile iFile; + RFs iFs; + }; + + +// The profiler engine itself. +class CProfiler : public CBase, private MProfilerEngine + { + enum {EControlPriority = 10, EReaderPriority = 0, EWriterPriority = -10}; + + /** Specifies the state of the engine*/ + enum TState + { + /** + Initial state. The file is closed. Driver is inactive + */ + EClosed, + /** + Engine enters this state on client's Start request (if -xiponly is not specified). + Opens the file. + Resets the driver and nonXIP code segments. + Sends GetSegments calls to the driver until driver returns zero length reply. + Leaves this state (goes into ERunning) when the last data (obtained by GetSegment) is + written into the file. + */ + EGettingSegments, + /** + Sends async. read request to the driver. Once completed, it immediately sends another while + writing the collected records into the file. + */ + ERunning, + /** + Get into this state from ERunning on the client's Stop, Close or Exit request. + Sends Drain calls to the driver until driver returns zero length reply. + Leaves this state when all records are written into the file. + */ + EDraining, + /** + No active calls to the driver. On the client's Start request, will go back into ERunning mode. + */ + EStopped, + /** + Get into this state on client's Close or Exit request. + Sends a single GetErrorReport request to the driver. After data has been recorded into the file, + it closes the file and goes into EClosed state or terminates application.. + */ + EGettingErrors + }; +public: + static CProfiler* NewLC(TInt aCmd, TDesC* aDrive); +// + TInt Control(Profiler::TState aCommand); +// + void ReadComplete(TBuffer* aBuf); + void WriteComplete(TBuffer* aBuf); +private: + CProfiler(); + ~CProfiler(); + void ConstructL(TInt aCmd, TDesC* aDrive); + MProfilerController* CreateUiL(); +// + void Read(); + void Write(); + TBool GetSegments(); + TBool Drain(); + void GetErrors(); +// + Profiler::TState State() const; +private: + CReader* iReader; + CWriter* iWriter; + MProfilerController* iServer; + RLibrary iUiCode; + MProfilerController* iUi; + TState iState; + TBool iXIPOnly; + Profiler::TState iLastCommand; +// + // The FIFO queue of data that has to be written + TBuffer* iHead; + TBuffer* iTail; +// + // The LIFO list of free buffers + TBuffer* iFree; + TDesC* iDrive; + }; + + +CProfiler* CProfiler::NewLC(TInt aCmd, TDesC* aDrive) + { + CProfiler* self = new(ELeave) CProfiler; + CleanupStack::PushL(self); + self->ConstructL(aCmd, aDrive); + return self; + } + +CProfiler::CProfiler() + {} + +CProfiler::~CProfiler() + { + delete iReader; + delete iWriter; + if (iServer) + iServer->Release(); + if (iUi) + iUi->Release(); + iUiCode.Close(); + + // discard the buffers in the free list + TBuffer* b=iFree; + while (b) + { + TBuffer* n = b->iNext; + delete b; + b = n; + } + + // discard any buffers in the holding queue + b=iHead; + while (b) + { + TBuffer* n = b->iNext; + delete b; + b = n; + } + } + +void CProfiler::ConstructL(TInt aCmd, TDesC* aDrive) +// +// Build the profiler engine +// + { + // Set drive letter of where to store profiler data + iDrive = aDrive; + + // Run the engine at maximum priority to try and ensure that the sampler device + // does not get choked and start dropping samples + RThread me; + me.SetPriority(EPriorityRealTime); + User::LeaveIfError(User::RenameThread(KProfilerName)); + + CActiveScheduler::Install(new(ELeave) CActiveScheduler); + iReader = new(ELeave) CReader(EReaderPriority,*this); + iReader->ConstructL(); + iWriter = new(ELeave) CWriter(EWriterPriority,*this); + iWriter->ConstructL(); + iServer = CPServer::NewL(EControlPriority,*this); + if (!(aCmd & KCommandNoUi)) + iUi = CreateUiL(); + + // Start off with two buffers in the free list for sample data + TBuffer* buf = new(ELeave) TBuffer; + buf->iNext = 0; + iFree = buf; + buf = new(ELeave) TBuffer; + buf->iNext = iFree; + iFree = buf; + + // idenify the running mode + iXIPOnly = aCmd & KCommandXIPOnly; + + // start profiling if requested + if ((aCmd & KCommandMask) == Profiler::EStart) + User::LeaveIfError(Control(Profiler::EStart)); + + } + +MProfilerController* CProfiler::CreateUiL() +// +// deal with the UI acquisition part of construction +// If ProfilerKeys.Dll is available, use it; otherwise create a text console +// + { + _LIT(KWindowServerName,"*WindowServer"); + TFindServer find(KWindowServerName); + TFullName n; + if (find.Next(n) == KErrNotFound) + { + // No UI on this device [yet]. Run without one. + return 0; + } + + if (iUiCode.Load(KProfilerKeysDll,TUidType(KNullUid, KUidProfilerKeys)) == KErrNone) + { + TProfilerControllerFactoryL factoryL = TProfilerControllerFactoryL(iUiCode.Lookup(1)); + MProfilerController* ui = NULL; + TRAPD(error, ui = factoryL(EControlPriority, *this)); + if (error == KErrNone) + return ui; + + // Couldn't create alternative UI, so use the console + iUiCode.Close(); + } + return CConsole::NewL(EControlPriority, *this); + } + +TInt CProfiler::Control(Profiler::TState aCommand) +// +// Handle a command from one of the controllers. +// This method specifies the flow of the engine state (iState attr). +// The most of transtions is not performed immediately but after all +// current data are recorded into the file - see WriteComplete method. +// + { + + DEBUG_PROFILER(RDebug::Printf("*CTRL %d",iState);) + + TInt r = KErrNone; + Profiler::TState oldCommand = iLastCommand; + + //Record the command. In most cases, it is WriteComplete method + //to perform state transition (based on this value) + iLastCommand = aCommand; + + switch (aCommand) + { + case Profiler::EStart: + switch (iState) + { + case EClosed: + { + // Set the path of the output file to include the drive letter + // specified at the command line or the default + TBuf path; + path.Copy(KFileName); + path[0] = (*iDrive)[0]; + r = iWriter->Open(path); + } + if (KErrNone != r) // Re-open the file + return r; + iReader->iSampler.Reset(iXIPOnly); // Reset the sampler + if(iXIPOnly) + { + iState = ERunning; + iReader->iSampler.Start(KSampleRate); // Start sampler + if (!iReader->IsActive()) + Read(); // Start reading + } + else + { + iState = EGettingSegments; + iReader->iSampler.ResetSegments(); // Reset segments + GetSegments(); // Start getting segments + } + break; + case EStopped: + iState = ERunning; + iReader->iSampler.Start(KSampleRate); // Start sampler + if (!iReader->IsActive()) + Read(); //Start reading + break; + case ERunning: //Already started. No action required. + case EGettingSegments: //Already started. No action required. + case EDraining: //Will restart after draining is completed. + case EGettingErrors: //Will restart after getting errors is completed; + break; + } + break; //end of case Profiler::EStart + + case Profiler::EStop: + switch (iState) + { + case EClosed: + case EGettingErrors: + iLastCommand = oldCommand; + return KErrGeneral; //The command makes no sense in this state + case ERunning: + iReader->iSampler.Stop(); //Stop sampling. + break; + case EGettingSegments: //Will do GettingSegments->Running->Stopped transitions + case EDraining: //Stopping already in progress + case EStopped: //Already stopped. + break; + } + break; //end of case Profiler::EStop + + case Profiler::EClose: + switch (iState) + { + case EStopped: + iState = EGettingErrors; + GetErrors(); + break; + case ERunning: + iReader->iSampler.Stop(); + break; + case EClosed: //Already closed. + case EGettingErrors: //Closing in progress + case EGettingSegments: + case EDraining: + break; + } + break; //end of case Profiler::EStop + + case Profiler::EUnload: + switch (iState) + { + case EClosed: + CActiveScheduler::Stop(); // Terminate application. + break; + case EStopped: + iState = EGettingErrors; + GetErrors(); + break; + case ERunning: + iReader->iSampler.Stop(); + break; + case EDraining: + case EGettingErrors: + case EGettingSegments: + break; + } + break;//end of case Profiler::Unload + } + + DEBUG_PROFILER(RDebug::Printf("*CTRL end %d",iState);) + return KErrNone; + } + +Profiler::TState CProfiler::State() const +// +// Report the current state of the engine +// + { + switch (iState) + { + case EGettingErrors: + case EStopped: + return Profiler::EStop; + case EClosed: + return Profiler::EClose; + default: + return Profiler::EStart; + } + } + +void CProfiler::Read() +// +// Pass a free buffer to the reader, allocating one if necessary +// + { + TBuffer* buf = iFree; + if (buf) + iFree = buf->iNext; + else + { + buf = new TBuffer; + if(!buf) + { + RDebug::Print(_L("PROFILER: No more memory ... stopping")); + CProfiler::Control(Profiler::EStop); + return; + } + } + iReader->Queue(buf); + } + +TBool CProfiler::GetSegments() +// +// Gets the list of the current non-XIP segments from device. +// Returns true if zero-length desc is returned, otherwise ... +// ...passes the buffer to write engine and returns false. + { + TBuffer* buf = iFree; + if (buf) + iFree = buf->iNext; + else + { + RDebug::Printf("PROFILER: No available buffer for GetSegments"); + User::Invariant(); + } + + iReader->iSampler.GetSegments(buf->iBuf); + if (!buf->iBuf.Length()) + { + buf->iNext = iFree;//Return empty buffer to the free list + iFree = buf; + return ETrue; + } + + iWriter->Queue(buf);//Pass the buffer to the write engine. + return EFalse; + } + +TBool CProfiler::Drain() +// +// Drains all remaining records from the device +// Returns true if zero-length desc is returned, otherwise ... +// ...passes the buffer to the write engine and returns false. + { + TBuffer* buf = iFree; + if (buf) + iFree = buf->iNext; + else + { + RDebug::Printf("PROFILER: No available buffer for Drain"); + User::Invariant(); + } + + iReader->iSampler.Drain(buf->iBuf); + + if (!buf->iBuf.Length()) + { + buf->iNext = iFree;//Return empty buffer to the free list + iFree = buf; + return ETrue; + } + iWriter->Queue(buf); //Pass the buffer to the write engine. + return EFalse; + } + + +void CProfiler::GetErrors() +// +// Gets error report from the device and pass the buffer to the write engine +// + { + TBuffer* buf = iFree; + if (buf) + iFree = buf->iNext; + else + { + RDebug::Printf("PROFILER: No available buffer for GetErrors"); + User::Invariant(); + } + iReader->iSampler.GetErrors(buf->iBuf); + iWriter->Queue(buf); + } + +void CProfiler::Write() +// +// Pass a queued buffer to the writer +// + { + TBuffer* buf = iHead; + iHead = buf->iNext; + if (iHead == 0) + iTail = 0; + iWriter->Queue(buf); + } + +void CProfiler::ReadComplete(TBuffer* aBuf) +// +// Handle a completed read buffer +// + { + DEBUG_PROFILER(RDebug::Printf("*RC %d",iState);) + + //Add the buffer to the queue + aBuf->iNext = 0; + if (iTail) + iTail->iNext = aBuf; + else + iHead = aBuf; + iTail = aBuf; + + if (!iWriter->IsActive()) + Write(); + + if (iLastCommand == Profiler::EStart) + Read(); //Request another read + + DEBUG_PROFILER(RDebug::Printf("*RC end %d",iState);) + } + +void CProfiler::WriteComplete(TBuffer* aBuf) +// +// Handle a flushed write buffer. +// + { + DEBUG_PROFILER(RDebug::Printf("*WC %d",iState);) + + aBuf->iNext = iFree;//Return empty buffer to the free list + iFree = aBuf; + + switch (iState) + { + case EGettingSegments: + if (!GetSegments()) + break;//More code segments to be completed + + //Always go to the running state after the segments are collected.... + iState = ERunning; + iReader->iSampler.Start(KSampleRate); + Read(); + + //...but stop sampler immediately if we got another user command + if (iLastCommand != Profiler::EStart) + { + iReader->iSampler.Stop(); + } + break; //the end of EGettingSegments case + + case ERunning: + if (iHead) + { + Write(); // There are more buffers to go to the file. + break; + } + if (iLastCommand != Profiler::EStart) + {//The user has stopped the profiler. + iState = EDraining; + if (!Drain()) + break;//More data to drain. + + //Drain returned empty. May progress further with the engine state + if (iLastCommand == Profiler::EStop) + iState = EStopped; + else + { + iState = EGettingErrors; + GetErrors(); + } + } + break;//the end of ERunning case + + case EDraining: + if (!Drain()) + break; //still draining; + + //Drain is completed + switch (iLastCommand) + { + case Profiler::EStart: + //While draining, we received another Start command + iState = ERunning; + iReader->iSampler.Start(KSampleRate); + Read(); + break; + case Profiler::EStop: + iState = EStopped; + break; + default: + iState = EGettingErrors; + GetErrors(); + } + break; //the end of EDraining case + + case EGettingErrors: + iWriter->Close(); + iState = EClosed; + switch (iLastCommand) + { + case Profiler::EUnload: + CActiveScheduler::Stop(); //Terminate application. + break; + case Profiler::EStart: + Control(Profiler::EStart); + break; + default: + break; + } + break; //the end of EGettingErrors case + + default: + RDebug::Printf("PROFILER: WriteComplete in %d state", iState); + User::Invariant(); + break; + + } + + DEBUG_PROFILER(RDebug::Printf("*WC end %d",iState);) + } + + + +CReader::CReader(TInt aPriority, CProfiler& aProfiler) + :CActive(aPriority), iProfiler(aProfiler) + { + CActiveScheduler::Add(this); + } + +CReader::~CReader() + { + Cancel(); + delete iBuf; + iSampler.Close(); + User::FreeLogicalDevice(KSamplerName); + } + +void CReader::ConstructL() + { + TInt r=User::LoadLogicalDevice(KSamplerName); + if (r!=KErrNone && r!=KErrAlreadyExists) + User::Leave(r); + User::LeaveIfError(iSampler.Open()); + } + +void CReader::RunL() +// +// Pass the full buffer to the engine +// + { + TBuffer* data=iBuf; + iBuf = 0; + iProfiler.ReadComplete(data); + } + +void CReader::DoCancel() + { + iSampler.ReadCancel(); + } + +void CReader::Queue(TBuffer* aBuf) +// +// Queue a request to read data into the empty buffer +// + { + iBuf = aBuf; + iSampler.Read(aBuf->iBuf, iStatus); + SetActive(); + } + +CWriter::CWriter(TInt aPriority, CProfiler& aProfiler) + :CActive(aPriority), iProfiler(aProfiler) + { + CActiveScheduler::Add(this); + } + +CWriter::~CWriter() + { + Cancel(); + delete iBuf; + iFile.Close(); + iFs.Close(); + } + +void CWriter::ConstructL() + { + User::LeaveIfError(iFs.Connect()); + } + +TInt CWriter::Open(const TDesC& aFile) +// +// Open the file for saving the sample data +// + { + return iFile.Replace(iFs,aFile,EFileWrite); + } + +void CWriter::Close() +// +// Release the file +// + { + iFile.Close(); + } + +void CWriter::Queue(TBuffer* aBuf) +// +// Queue a request to write the full buffer into the file +// + { + iBuf = aBuf; + iFile.Write(aBuf->iBuf, iStatus); + SetActive(); + } + +void CWriter::RunL() +// +// Return the empty buffer back to the engine +// + { + TBuffer* data=iBuf; + iBuf = 0; + iProfiler.WriteComplete(data); + } + +void CWriter::DoCancel() +// +// RFile does not provide a WriteCancel() function +// + {} + + +// Server controller + +inline const CPServer& CPSession::Server() const + {return *static_cast(CSession2::Server());} + +void CPSession::ServiceL(const RMessage2& aMessage) +// +// Handle a IPC request to control the profiler +// + { + aMessage.Complete(Server().Control(Profiler::TState(aMessage.Function()))); + } + +MProfilerController* CPServer::NewL(TInt aPriority, MProfilerEngine& aEngine) +// +// Create and start the server to provide the Profiler interface +// + { + CPServer* self = new(ELeave) CPServer(aPriority, aEngine); + CleanupStack::PushL(self); + self->StartL(KProfilerName); + CleanupStack::Pop(); + return self; + } + +CPServer::CPServer(TInt aPriority, MProfilerEngine& aEngine) + :CServer2(aPriority), MProfilerController(aEngine) + {} + +void CPServer::Release() + { + delete this; + } + +CSession2* CPServer::NewSessionL(const TVersion&,const RMessage2&) const + { + return new(ELeave) CPSession(); + } + + +// Console Controller + +MProfilerController* CConsole::NewL(TInt aPriority, MProfilerEngine& aEngine) +// +// Create and start the console UI for the profiler +// + { + CConsole* self = new(ELeave) CConsole(aPriority, aEngine); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } + +CConsole::CConsole(TInt aPriority, MProfilerEngine& aEngine) + :CActive(aPriority), MProfilerController(aEngine) + { + CActiveScheduler::Add(this); + } + +void CConsole::ConstructL() + { + iConsole = Console::NewL(KProfilerName, TSize(KConsFullScreen,KConsFullScreen)); + Help(); + Queue(); + } + +CConsole::~CConsole() + { + Cancel(); + delete iConsole; + } + +void CConsole::Release() + { + delete this; + } + +void CConsole::Help() +// +// Display the instructions on the console +// + { + _LIT(KInstructions,"[S]tart, Sto[p], [C]lose or E[x]it\r\n"); + iConsole->Write(KInstructions); + } + +void CConsole::Queue() +// +// Request a key press from the console +// + { + iConsole->Read(iStatus); + SetActive(); + } + +void CConsole::RunL() +// +// Handle a key press from the console +// + { + TInt key = iConsole->KeyCode(); + Queue(); + Profiler::TState command; + switch (key) + { + case 's': case 'S': + command = Profiler::EStart; + break; + case 'p': case 'P': + command = Profiler::EStop; + break; + case 'c': case 'C': + command = Profiler::EClose; + break; + case 'x': case 'X': + command = Profiler::EUnload; + break; + case '?': case 'h': case 'H': + Help(); + return; + default: + return; + } + Control(command); + } + +void CConsole::DoCancel() + { + iConsole->ReadCancel(); + } + + +void MainL(TInt aCmd, TDesC* aDrive) +// +// Construct and run the profile engine +// + { + CProfiler* p = CProfiler::NewLC(aCmd, aDrive); + CActiveScheduler::Start(); + CleanupStack::PopAndDestroy(p); + } + +TInt GetCommand(TDes &aDrive) +// +// Decode the command line arguments into a profiler control request +// aDrive is the drive number to store the profiler data on +// + { + _LIT(KStart,"start"); + _LIT(KStop,"stop"); + _LIT(KClose,"close"); + _LIT(KUnload,"unload"); + _LIT(KExit,"exit"); + _LIT(KNoUi,"-noui"); + _LIT(KXIPOnly,"-xiponly"); + _LIT(KDrive,"-drive="); + const TInt KDriveOffset=7; + TBuf<64> c; + User::CommandLine(c); + TInt cmd = 0; + if (c.FindF(KNoUi) >= 0) + cmd |= KCommandNoUi; + if (c.FindF(KXIPOnly) >= 0) + cmd |= KCommandXIPOnly; + + // get the drive letter if any + TInt pos = c.FindF(KDrive); + if(pos >= 0) + { + pos += KDriveOffset; + TBuf<1> driveLet; + driveLet.SetLength(1); + driveLet[0] = c[pos]; + driveLet.UpperCase(); + if (driveLet[0] >= 'A' && driveLet[0] <= 'Z') + { + aDrive[0] = driveLet[0]; + } + } + if (c.FindF(KStart) >= 0) + return cmd | Profiler::EStart; + if (c.FindF(KStop) >= 0) + return cmd | Profiler::EStop; + if (c.FindF(KClose) >= 0) + return cmd | Profiler::EClose; + if (c.FindF(KUnload) >= 0) + return cmd | Profiler::EUnload; + if (c.FindF(KExit) >= 0) + return cmd | Profiler::EUnload; + return cmd | KCommandNone; + } + +TInt E32Main() +// +// Profiler.exe entry point +// Decode any command-line argument - which can be used to control a running profile engine +// Otherwise start the engine in this process +// + { + TBuf<1> drive; + drive.SetLength(1); + drive[0] = 'C'; + TInt command = GetCommand(drive); + if ((command & KCommandMask) != KCommandNone) + { + TInt r = Profiler::Control(Profiler::TState(command & KCommandMask)); + if (r != KErrNotFound || (command & KCommandMask) != Profiler::EStart) + return r; + } + CTrapCleanup::New(); + TRAPD(r,MainL(command, &drive)); + if (r != KErrNone) + RDebug::Print(_L("PROFILER: Error starting profiler")); + return r; + }