|
1 // topfshell.cpp |
|
2 // |
|
3 // Copyright (c) 2008 - 2010 Accenture. All rights reserved. |
|
4 // This component and the accompanying materials are made available |
|
5 // under the terms of the "Eclipse Public License v1.0" |
|
6 // which accompanies this distribution, and is available |
|
7 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 // |
|
9 // Initial Contributors: |
|
10 // Accenture - Initial contribution |
|
11 // |
|
12 |
|
13 #include <e32hashtab.h> |
|
14 #include <fshell/ioutils.h> |
|
15 #include <fshell/memoryaccesscmd.h> |
|
16 #include "sampler.h" |
|
17 |
|
18 using namespace IoUtils; |
|
19 |
|
20 class CCmdTop : public CMemoryAccessCommandBase |
|
21 { |
|
22 public: |
|
23 static CCommandBase* NewLC(); |
|
24 ~CCmdTop(); |
|
25 private: |
|
26 CCmdTop(); |
|
27 static TInt TimerCallback(TAny* aSelf); |
|
28 void TimerCallback(); |
|
29 void UpdateL(); |
|
30 struct SThreadData; |
|
31 static TInt SortReverse(const CCmdTop::SThreadData& aLeft, const CCmdTop::SThreadData& aRight); |
|
32 |
|
33 private: // From CCommandBase. |
|
34 virtual const TDesC& Name() const; |
|
35 virtual void DoRunL(); |
|
36 virtual void ArgumentsL(RCommandArgumentList& aArguments); |
|
37 virtual void OptionsL(RCommandOptionList& aOptions); |
|
38 private: |
|
39 RBuf8 iReadBuf; |
|
40 struct SThreadData |
|
41 { |
|
42 TUint iId; |
|
43 TInt iNumSamples; |
|
44 TFullName iName; // Saves recalculating every time |
|
45 }; |
|
46 RHashMap<TUint, SThreadData*> iSeenThreads; // Saves linear searching through iThreads for each sample (Maps thread id to SThreadData |
|
47 RPointerArray<SThreadData> iThreads; |
|
48 RSampler iSampler; |
|
49 CPeriodic* iTimer; |
|
50 TInt iRate; |
|
51 |
|
52 CTextBuffer* iBuffer; |
|
53 CTextFormatter* iFormatter; |
|
54 TInt iNumConsoleLines; |
|
55 TInt iNumLinesInLastUpdate; |
|
56 }; |
|
57 |
|
58 CCommandBase* CCmdTop::NewLC() |
|
59 { |
|
60 CCmdTop* self = new(ELeave) CCmdTop(); |
|
61 CleanupStack::PushL(self); |
|
62 self->BaseConstructL(); |
|
63 return self; |
|
64 } |
|
65 |
|
66 CCmdTop::~CCmdTop() |
|
67 { |
|
68 if (iTimer) |
|
69 { |
|
70 iTimer->Cancel(); |
|
71 } |
|
72 delete iTimer; |
|
73 if (iSampler.Handle()) |
|
74 { |
|
75 iSampler.Stop(); |
|
76 iSampler.Close(); |
|
77 User::FreeLogicalDevice(KSamplerName); |
|
78 } |
|
79 delete iBuffer; |
|
80 delete iFormatter; |
|
81 iReadBuf.Close(); |
|
82 iThreads.ResetAndDestroy(); |
|
83 iSeenThreads.Close(); |
|
84 } |
|
85 |
|
86 CCmdTop::CCmdTop() |
|
87 : CMemoryAccessCommandBase(EManualComplete), iRate(1000) |
|
88 { |
|
89 } |
|
90 |
|
91 const TDesC& CCmdTop::Name() const |
|
92 { |
|
93 _LIT(KName, "top"); |
|
94 return KName; |
|
95 } |
|
96 |
|
97 void CCmdTop::ArgumentsL(RCommandArgumentList& /*aArguments*/) |
|
98 { |
|
99 } |
|
100 |
|
101 void CCmdTop::OptionsL(RCommandOptionList& aOptions) |
|
102 { |
|
103 aOptions.AppendIntL(iRate, _L("rate")); |
|
104 } |
|
105 |
|
106 EXE_BOILER_PLATE(CCmdTop) |
|
107 |
|
108 void CCmdTop::DoRunL() |
|
109 { |
|
110 #ifdef FSHELL_MEMORY_ACCESS_SUPPORT |
|
111 LoadMemoryAccessL(); |
|
112 #endif |
|
113 |
|
114 TInt err = User::LoadLogicalDevice(KSamplerName); |
|
115 if (err != KErrNone && err != KErrAlreadyExists) |
|
116 { |
|
117 LeaveIfErr(err, _L("Couldn't load sampler ldd %S"), &KSamplerName); |
|
118 } |
|
119 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 |
|
120 iTimer = CPeriodic::NewL(CActive::EPriorityStandard); |
|
121 iTimer->Start(iRate*1000, iRate*1000, TCallBack(&CCmdTop::TimerCallback, this)); |
|
122 |
|
123 LeaveIfErr(iSampler.Open(), _L("Couldn't open sampler")); |
|
124 iSampler.Reset(ETrue); // Parameter is ignored |
|
125 iSampler.Start(1000); // Always tell sampler to sample every millisecond, regardless of the frequency we update the UI |
|
126 |
|
127 TSize size; |
|
128 User::LeaveIfError(Stdout().GetScreenSize(size)); |
|
129 iNumConsoleLines = size.iHeight; |
|
130 iBuffer = CTextBuffer::NewL(512); |
|
131 iFormatter = CTextFormatter::NewL(size.iWidth); |
|
132 Stdout().ClearScreen(); |
|
133 UpdateL(); // Just so we display something on screen before the first update |
|
134 } |
|
135 |
|
136 TInt CCmdTop::TimerCallback(TAny* aSelf) |
|
137 { |
|
138 static_cast<CCmdTop*>(aSelf)->TimerCallback(); |
|
139 return 0; |
|
140 } |
|
141 |
|
142 void CCmdTop::TimerCallback() |
|
143 { |
|
144 iReadBuf.Zero(); |
|
145 TRequestStatus stat; |
|
146 iSampler.Read(iReadBuf, stat); |
|
147 User::WaitForRequest(stat); // API used to be async, it's now sync but I haven't changed the interface |
|
148 |
|
149 // First off clear all the iNumSamples (as that was for the previous sampling period) |
|
150 for (TInt i = 0; i < iThreads.Count(); i++) |
|
151 { |
|
152 iThreads[i]->iNumSamples = 0; |
|
153 } |
|
154 |
|
155 TInt numSamples = iReadBuf.Length() / sizeof(TUint32); |
|
156 TUint32* buf = (TUint32*)iReadBuf.Ptr(); |
|
157 for (TInt i = 0; i < numSamples; i++) |
|
158 { |
|
159 TUint32 threadId = buf[i]; |
|
160 SThreadData** thread = iSeenThreads.Find(threadId); |
|
161 if (thread) |
|
162 { |
|
163 (*thread)->iNumSamples++; |
|
164 } |
|
165 else |
|
166 { |
|
167 // New thread id |
|
168 SThreadData* thread = new SThreadData; |
|
169 if (!thread) continue; |
|
170 TInt err = iThreads.Append(thread); |
|
171 if (err) |
|
172 { |
|
173 delete thread; |
|
174 continue; |
|
175 } |
|
176 err = iSeenThreads.Insert(threadId, thread); |
|
177 if (err) |
|
178 { |
|
179 iThreads.Remove(iThreads.Count()-1); |
|
180 delete thread; |
|
181 continue; |
|
182 } |
|
183 thread->iNumSamples = 1; |
|
184 thread->iId = threadId; |
|
185 RThread rthread; |
|
186 #ifdef FSHELL_MEMORY_ACCESS_SUPPORT |
|
187 err = iMemAccess.RThreadForceOpen(rthread, threadId); |
|
188 #else |
|
189 err = rthread.Open(TThreadId(threadId)); |
|
190 #endif |
|
191 if (err == KErrNone) |
|
192 { |
|
193 thread->iName = rthread.FullName(); |
|
194 //PrettyName(EListThread, thread->iName); |
|
195 rthread.Close(); |
|
196 } |
|
197 else |
|
198 { |
|
199 thread->iName = _L("Tid: "); |
|
200 thread->iName.AppendNum(threadId); |
|
201 } |
|
202 } |
|
203 } |
|
204 TRAP_IGNORE(UpdateL()); |
|
205 } |
|
206 |
|
207 TInt CCmdTop::SortReverse(const CCmdTop::SThreadData& aLeft, const CCmdTop::SThreadData& aRight) |
|
208 { |
|
209 // This sorts largest number of samples first |
|
210 TInt res = - (aLeft.iNumSamples - aRight.iNumSamples); |
|
211 if (res == 0) return aLeft.iId - aRight.iId; // make sure there is a total ordering in event of tie break |
|
212 return res; |
|
213 } |
|
214 |
|
215 void CCmdTop::UpdateL() |
|
216 { |
|
217 // First, sort iThreads |
|
218 iThreads.Sort(TLinearOrder<SThreadData>(&SortReverse)); |
|
219 |
|
220 TInt numSamples = iReadBuf.Size() / sizeof(TUint32); |
|
221 |
|
222 iBuffer->Zero(); |
|
223 iFormatter->Zero(); |
|
224 iBuffer->AppendL(_L("Tid\tThread name\tCPU usage\r\n")); |
|
225 |
|
226 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 |
|
227 { |
|
228 SThreadData& thread = *iThreads[i]; |
|
229 TReal percent = 100 * thread.iNumSamples / (TReal)numSamples; |
|
230 iBuffer->AppendFormatL(_L("%d\t%S\t%00.2f%%\r\n"), thread.iId, &thread.iName, percent); |
|
231 } |
|
232 Stdout().SetCursorHeight(0); |
|
233 iFormatter->TabulateL(0, 1, iBuffer->Descriptor(), ETruncateLongestColumn); |
|
234 User::LeaveIfError(Stdout().SetCursorPosAbs(TPoint(0, 0))); |
|
235 Stdout().Write(iFormatter->Descriptor()); |
|
236 TInt numLines = 1 + iThreads.Count(); // plus 1 for the title line |
|
237 TInt numOldLines = iNumLinesInLastUpdate - numLines; |
|
238 while (numOldLines > 0) |
|
239 { |
|
240 Stdout().ClearToEndOfLine(); |
|
241 Stdout().SetCursorPosRel(TPoint(0, 1)); |
|
242 --numOldLines; |
|
243 } |
|
244 iNumLinesInLastUpdate = numLines; |
|
245 } |