Profiler.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. #include "Profiler.h"
  2. #include "DebugManager.h"
  3. #include <mmsystem.h>
  4. #include <ntsecapi.h> // UNICODE_STRING
  5. #define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
  6. typedef LONG KPRIORITY;
  7. USING_NS_BF_DBG;
  8. enum SYSTEM_INFORMATION_CLASS
  9. {
  10. SystemProcessInformation = 5
  11. }; // SYSTEM_INFORMATION_CLASS
  12. struct CLIENT_ID
  13. {
  14. HANDLE UniqueProcess;
  15. HANDLE UniqueThread;
  16. }; // struct CLIENT_ID
  17. enum THREAD_STATE
  18. {
  19. StateInitialized,
  20. StateReady,
  21. StateRunning,
  22. StateStandby,
  23. StateTerminated,
  24. StateWait,
  25. StateTransition,
  26. StateUnknown
  27. };
  28. struct VM_COUNTERS
  29. {
  30. SIZE_T PeakVirtualSize;
  31. SIZE_T VirtualSize;
  32. ULONG PageFaultCount;
  33. SIZE_T PeakWorkingSetSize;
  34. SIZE_T WorkingSetSize;
  35. SIZE_T QuotaPeakPagedPoolUsage;
  36. SIZE_T QuotaPagedPoolUsage;
  37. SIZE_T QuotaPeakNonPagedPoolUsage;
  38. SIZE_T QuotaNonPagedPoolUsage;
  39. SIZE_T PagefileUsage;
  40. SIZE_T PeakPagefileUsage;
  41. SIZE_T PrivatePageCount;
  42. };
  43. struct SYSTEM_THREAD {
  44. LARGE_INTEGER KernelTime;
  45. LARGE_INTEGER UserTime;
  46. LARGE_INTEGER CreateTime;
  47. ULONG WaitTime;
  48. LPVOID StartAddress;
  49. CLIENT_ID ClientId;
  50. DWORD Priority;
  51. LONG BasePriority;
  52. ULONG ContextSwitchCount;
  53. THREAD_STATE State;
  54. ULONG WaitReason;
  55. };
  56. struct SYSTEM_PROCESS_INFORMATION
  57. {
  58. ULONG NextEntryOffset;
  59. ULONG NumberOfThreads;
  60. LARGE_INTEGER Reserved[3];
  61. LARGE_INTEGER CreateTime;
  62. LARGE_INTEGER UserTime;
  63. LARGE_INTEGER KernelTime;
  64. UNICODE_STRING ImageName;
  65. KPRIORITY BasePriority;
  66. HANDLE ProcessId;
  67. HANDLE InheritedFromProcessId;
  68. ULONG HandleCount;
  69. ULONG Reserved2[2];
  70. ULONG PrivatePageCount;
  71. VM_COUNTERS VirtualMemoryCounters;
  72. IO_COUNTERS IoCounters;
  73. SYSTEM_THREAD Threads[1];
  74. };
  75. typedef NTSTATUS(NTAPI *NtQuerySystemInformation_t)(SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG);
  76. static NtQuerySystemInformation_t NtQuerySystemInformation = NULL;
  77. static HMODULE ntdll = NULL;
  78. DbgProfiler::DbgProfiler(WinDebugger* debugger) : mShutdownEvent(true)
  79. {
  80. mDebugger = debugger;
  81. mIsRunning = false;
  82. mWantsClear = false;
  83. mSamplesPerSecond = 1000;
  84. mTotalVirtualSamples = 0;
  85. mTotalActualSamples = 0;
  86. mTotalActiveSamplingMS = 0;
  87. mStartTick = BFTickCount();
  88. mEndTick = 0;
  89. mDebugger->AddProfiler(this);
  90. }
  91. DbgProfiler::~DbgProfiler()
  92. {
  93. mDebugger->RemoveProfiler(this);
  94. Stop();
  95. DoClear();
  96. }
  97. static SYSTEM_PROCESS_INFORMATION* CaptureProcessInfo()
  98. {
  99. WCHAR path[MAX_PATH];
  100. GetSystemDirectory(path, MAX_PATH);
  101. wcscat(path, L"\\ntdll.dll");
  102. ntdll = GetModuleHandle(path);
  103. if (ntdll == NULL)
  104. return NULL;
  105. NtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(ntdll, "NtQuerySystemInformation");
  106. uint allocSize = 1024;
  107. uint8* data = NULL;
  108. while (true)
  109. {
  110. data = new uint8[allocSize];
  111. ULONG wantSize = 0;
  112. NTSTATUS status = ::NtQuerySystemInformation(SystemProcessInformation, data, allocSize, &wantSize);
  113. if (status != STATUS_INFO_LENGTH_MISMATCH)
  114. return (SYSTEM_PROCESS_INFORMATION*)data;
  115. allocSize = wantSize + 4096;
  116. delete data;
  117. }
  118. }
  119. void DbgProfiler::DoClear()
  120. {
  121. for (auto& kv : mThreadInfo)
  122. delete kv.mValue;
  123. for (auto& val : mUniqueProcSet)
  124. delete val.mProcId;
  125. }
  126. ProfileProcId* DbgProfiler::Get(const StringImpl& str)
  127. {
  128. ProfileProdIdEntry checkEntry;
  129. checkEntry.mProcId = (ProfileProcId*)&str;
  130. ProfileProdIdEntry* entryPtr;
  131. if (mUniqueProcSet.TryAdd(checkEntry, &entryPtr))
  132. {
  133. auto procId = new ProfileProcId();
  134. procId->mProcName = str;
  135. entryPtr->mProcId = procId;
  136. return procId;
  137. }
  138. else
  139. {
  140. return entryPtr->mProcId;
  141. }
  142. }
  143. void DbgProfiler::ThreadProc()
  144. {
  145. //TODO: Do timing smarter, handle delays and slow stack traces and stuff
  146. //timeBeginPeriod(1);
  147. BF_ASSERT(mTotalVirtualSamples == 0);
  148. ProfileAddrEntry profileEntry;
  149. const int maxStackTrace = 1024;
  150. addr_target stackTrace[maxStackTrace];
  151. DWORD prevSampleTick = timeGetTime();
  152. uint32 accumMS = 0;
  153. int totalWait = 0;
  154. int totalWait2 = 0;
  155. int iterations = 0;
  156. HashSet<int> idleThreadSet;
  157. while (true)
  158. {
  159. if (mWantsClear)
  160. {
  161. DoClear();
  162. mTotalVirtualSamples = 0;
  163. mTotalActualSamples = 0;
  164. mTotalActiveSamplingMS = 0;
  165. mAlloc.Clear();
  166. mThreadInfo.Clear();
  167. mThreadIdList.Clear();
  168. mProfileAddrEntrySet.Clear();
  169. mProfileAddrEntries.Clear();
  170. mPendingProfileEntries.Clear();
  171. mProfileProcEntrySet.Clear();
  172. mProfileProcEntries.Clear();
  173. mProfileAddrToProcMap.Clear();
  174. mProcMap.Clear();
  175. mUniqueProcSet.Clear();
  176. mWantsClear = false;
  177. accumMS = 0;
  178. prevSampleTick = timeGetTime();
  179. }
  180. iterations++;
  181. DWORD startTick0 = timeGetTime();
  182. idleThreadSet.Clear();
  183. SYSTEM_PROCESS_INFORMATION* processData = NULL;
  184. std::unique_ptr<SYSTEM_PROCESS_INFORMATION> ptrDelete(processData);
  185. auto curProcessData = processData;
  186. while (true)
  187. {
  188. if (processData == NULL)
  189. break;
  190. if ((DWORD)(uintptr)curProcessData->ProcessId == mDebugger->mProcessInfo.dwProcessId)
  191. {
  192. for (int threadIdx = 0; threadIdx < (int)curProcessData->NumberOfThreads; threadIdx++)
  193. {
  194. auto& threadInfo = curProcessData->Threads[threadIdx];
  195. if ((threadInfo.State == StateWait) || (threadInfo.State == StateTerminated))
  196. idleThreadSet.Add((int)(intptr)threadInfo.ClientId.UniqueThread);
  197. }
  198. }
  199. if (curProcessData->NextEntryOffset == 0)
  200. break;
  201. curProcessData = (SYSTEM_PROCESS_INFORMATION*)((intptr)curProcessData + curProcessData->NextEntryOffset);
  202. }
  203. if (mShutdownEvent.WaitFor(0))
  204. {
  205. break;
  206. }
  207. DWORD tickNow = timeGetTime();
  208. accumMS += (int)(tickNow - prevSampleTick);
  209. prevSampleTick = tickNow;
  210. int wantVirtualSamples = (int)((int64)accumMS * mSamplesPerSecond / 1000);
  211. int curSampleCount = wantVirtualSamples - mTotalVirtualSamples;
  212. BF_ASSERT(curSampleCount >= 0);
  213. if (curSampleCount == 0)
  214. continue;
  215. {
  216. AutoCrit autoCrit(mDebugger->mDebugManager->mCritSect);
  217. if ((mDebugger->mRunState == RunState_NotStarted) ||
  218. (mDebugger->mRunState == RunState_Terminated))
  219. break;
  220. if (mDebugger->mRunState != RunState_Running)
  221. {
  222. // Reset timer
  223. accumMS = (int)((int64)mTotalVirtualSamples * 1000 / mSamplesPerSecond);
  224. continue;
  225. }
  226. }
  227. int maxSamplesPerIteration = mSamplesPerSecond / 10;
  228. if (curSampleCount > maxSamplesPerIteration)
  229. {
  230. curSampleCount = maxSamplesPerIteration;
  231. wantVirtualSamples = mTotalVirtualSamples + curSampleCount;
  232. accumMS = (int)((int64)wantVirtualSamples * 1000 / mSamplesPerSecond);
  233. }
  234. mTotalVirtualSamples += curSampleCount;
  235. mTotalActualSamples++;
  236. int threadIdx = 0;
  237. while (true)
  238. {
  239. int startTick = timeGetTime();
  240. AutoCrit autoCrit(mDebugger->mDebugManager->mCritSect);
  241. if (mDebugger->mRunState != RunState_Running)
  242. break;
  243. if (threadIdx >= mDebugger->mThreadList.size())
  244. break;
  245. auto thread = mDebugger->mThreadList[threadIdx];
  246. if ((mTargetThreadId > 0) && (thread->mThreadId != mTargetThreadId))
  247. {
  248. threadIdx++;
  249. continue;
  250. }
  251. ProfileThreadInfo* profileThreadInfo;
  252. ProfileThreadInfo** profileThreadInfoPtr;
  253. if (mThreadInfo.TryAdd(thread->mThreadId, NULL, &profileThreadInfoPtr))
  254. {
  255. mDebugger->TryGetThreadName(thread);
  256. profileThreadInfo = new ProfileThreadInfo();
  257. profileThreadInfo->mName = thread->mName;
  258. *profileThreadInfoPtr = profileThreadInfo;
  259. mThreadIdList.Add(thread->mThreadId);
  260. }
  261. else
  262. {
  263. profileThreadInfo = *profileThreadInfoPtr;
  264. }
  265. profileThreadInfo->mTotalSamples += curSampleCount;
  266. bool isThreadIdle = idleThreadSet.Contains(thread->mThreadId);
  267. mDebugger->mActiveThread = thread;
  268. ::SuspendThread(thread->mHThread);
  269. CPURegisters registers;
  270. mDebugger->PopulateRegisters(&registers);
  271. int stackSize = 0;
  272. for (int stackIdx = 0 ; stackIdx < maxStackTrace; stackIdx++)
  273. {
  274. auto pc = registers.GetPC();
  275. if (pc <= 0xFFFF)
  276. {
  277. break;
  278. }
  279. stackTrace[stackSize++] = pc;
  280. auto prevSP = registers.GetSP();
  281. if (!mDebugger->RollBackStackFrame(&registers, stackIdx == 0))
  282. break;
  283. if (registers.GetSP() <= prevSP)
  284. {
  285. // SP went the wrong direction, stop rolling back
  286. break;
  287. }
  288. }
  289. ProfileAddrEntry* insertedProfileEntry = AddToSet(mProfileAddrEntrySet, stackTrace, stackSize);
  290. if (insertedProfileEntry->mEntryIdx == -1)
  291. {
  292. insertedProfileEntry->mEntryIdx = (int)mProfileAddrEntrySet.size(); // Starts at '1'
  293. mPendingProfileEntries.Add(*insertedProfileEntry);
  294. }
  295. for (int i = 0; i < curSampleCount; i++)
  296. {
  297. int entryIdx = insertedProfileEntry->mEntryIdx;
  298. if (isThreadIdle)
  299. entryIdx = -entryIdx;
  300. profileThreadInfo->mProfileAddrEntries.push_back(entryIdx);
  301. }
  302. ::ResumeThread(thread->mHThread);
  303. int elapsedTime = timeGetTime() - startTick;
  304. mTotalActiveSamplingMS += elapsedTime;
  305. mDebugger->mActiveThread = NULL;
  306. threadIdx++;
  307. HandlePendingEntries();
  308. }
  309. }
  310. mIsRunning = false;
  311. mEndTick = BFTickCount();
  312. }
  313. static void BFP_CALLTYPE ThreadProcThunk(void* profiler)
  314. {
  315. ((DbgProfiler*)profiler)->ThreadProc();
  316. }
  317. void DbgProfiler::Start()
  318. {
  319. BF_ASSERT(!mIsRunning);
  320. mNeedsProcessing = true;
  321. mIsRunning = true;
  322. auto thread = BfpThread_Create(ThreadProcThunk, (void*)this, 128 * 1024, BfpThreadCreateFlag_StackSizeReserve);
  323. BfpThread_Release(thread);
  324. }
  325. void DbgProfiler::Stop()
  326. {
  327. mShutdownEvent.Set();
  328. while (mIsRunning)
  329. BfpThread_Yield();
  330. //Process();
  331. }
  332. void DbgProfiler::Clear()
  333. {
  334. mWantsClear = true;
  335. }
  336. String DbgProfiler::GetOverview()
  337. {
  338. String str;
  339. uint32 curTick = BFTickCount();
  340. if (mEndTick == 0)
  341. str += StrFormat("%d", curTick - mStartTick);
  342. else
  343. str += StrFormat("%d\t%d", mEndTick - mStartTick, curTick - mEndTick);
  344. str += "\n";
  345. str += StrFormat("%d\t%d\t%d\n", mSamplesPerSecond, mTotalVirtualSamples, mTotalActualSamples);
  346. str += mDescription;
  347. return str;
  348. }
  349. String DbgProfiler::GetThreadList()
  350. {
  351. BF_ASSERT(!mIsRunning);
  352. String result;
  353. for (auto threadId : mThreadIdList)
  354. {
  355. auto threadInfo = mThreadInfo[threadId];
  356. result += StrFormat("%d\t%s\n", threadId, threadInfo->mName.c_str());
  357. }
  358. return result;
  359. }
  360. void DbgProfiler::AddEntries(String& str, Array<ProfileProcEntry*>& procEntries, int rangeStart, int rangeEnd, int stackIdx, ProfileProcId* findProc)
  361. {
  362. struct _QueuedEntry
  363. {
  364. int mRangeIdx;
  365. int mRangeEnd;
  366. int mStackIdx;
  367. };
  368. Array<_QueuedEntry> workQueue;
  369. auto _AddEntries = [&](int rangeStart, int rangeEnd, int stackIdx, ProfileProcId* findProc)
  370. {
  371. int selfSampleCount = 0;
  372. int childSampleCount = 0;
  373. // First arrange list so we only contain items that match 'findProc'
  374. if (stackIdx != -1)
  375. {
  376. for (int idx = rangeStart; idx < rangeEnd; idx++)
  377. {
  378. auto procEntry = procEntries[idx];
  379. if (procEntry->mUsed)
  380. continue;
  381. // The range should only contain candidates
  382. BF_ASSERT(procEntry->mSize > stackIdx);
  383. auto curProc = procEntry->mData[procEntry->mSize - 1 - stackIdx];
  384. bool doRemove = false;
  385. if (findProc != curProc)
  386. {
  387. // Not a match
  388. doRemove = true;
  389. }
  390. else
  391. {
  392. if (procEntry->mSize == stackIdx + 1)
  393. {
  394. BF_ASSERT(selfSampleCount == 0);
  395. selfSampleCount = procEntry->mSampleCount;
  396. // We are a the full actual entry, so we're done here...
  397. procEntry->mUsed = true;
  398. doRemove = true;
  399. }
  400. else
  401. {
  402. childSampleCount += procEntry->mSampleCount;
  403. }
  404. }
  405. if (doRemove)
  406. {
  407. // 'fast remove' entry
  408. BF_SWAP(procEntries[rangeEnd - 1], procEntries[idx]);
  409. rangeEnd--;
  410. idx--;
  411. continue;
  412. }
  413. }
  414. if (findProc == NULL)
  415. str += "???";
  416. else
  417. str += findProc->mProcName;
  418. char cStr[256];
  419. sprintf(cStr, "\t%d\t%d\n", selfSampleCount, childSampleCount);
  420. str += cStr;
  421. }
  422. _QueuedEntry entry;
  423. entry.mRangeIdx = rangeStart;
  424. entry.mRangeEnd = rangeEnd;
  425. entry.mStackIdx = stackIdx;
  426. workQueue.Add(entry);
  427. };
  428. _AddEntries(rangeStart, rangeEnd, stackIdx, findProc);
  429. while (!workQueue.IsEmpty())
  430. {
  431. auto& entry = workQueue.back();
  432. bool addedChild = false;
  433. while (entry.mRangeIdx < entry.mRangeEnd)
  434. {
  435. auto procEntry = procEntries[entry.mRangeIdx];
  436. if (procEntry->mUsed)
  437. {
  438. entry.mRangeIdx++;
  439. continue;
  440. }
  441. int nextStackIdx = entry.mStackIdx + 1;
  442. auto nextFindProc = procEntry->mData[procEntry->mSize - 1 - nextStackIdx];
  443. _AddEntries(entry.mRangeIdx, entry.mRangeEnd, nextStackIdx, nextFindProc);
  444. addedChild = true;
  445. break;
  446. }
  447. if (!addedChild)
  448. {
  449. if (entry.mStackIdx != -1)
  450. str += "-\n";
  451. workQueue.pop_back();
  452. }
  453. }
  454. }
  455. void DbgProfiler::HandlePendingEntries()
  456. {
  457. bool reverse = false;
  458. const int maxStackTrace = 1024;
  459. ProfileProcId* procStackTrace[maxStackTrace];
  460. while (mProfileAddrToProcMap.size() < mProfileAddrEntrySet.size() + 1)
  461. mProfileAddrToProcMap.push_back(-1);
  462. for (int addrEntryIdx = 0; addrEntryIdx < (int)mPendingProfileEntries.size(); addrEntryIdx++)
  463. {
  464. const ProfileAddrEntry* addrEntry = &mPendingProfileEntries[addrEntryIdx];
  465. ProfileProcId** procStackTraceHead = reverse ? (procStackTrace + maxStackTrace) : procStackTrace;
  466. int stackTraceProcIdx = 0;
  467. for (int addrIdx = 0; addrIdx < addrEntry->mSize; addrIdx++)
  468. {
  469. addr_target addr = addrEntry->mData[addrIdx];
  470. if (addrIdx > 0)
  471. {
  472. // To reference from SourcePC (calling address, not return address)
  473. addr--;
  474. }
  475. ProfileProcId* procId = NULL;
  476. auto subProgram = mDebugger->mDebugTarget->FindSubProgram(addr, DbgOnDemandKind_LocalOnly);
  477. while (stackTraceProcIdx < maxStackTrace)
  478. {
  479. if (subProgram != NULL)
  480. {
  481. ProfileProcId** procIdPtr;
  482. if (mProcMap.TryAdd(subProgram, NULL, &procIdPtr))
  483. {
  484. String procName = subProgram->ToString();
  485. procId = Get(procName);
  486. *procIdPtr = procId;
  487. }
  488. else
  489. procId = *procIdPtr;
  490. }
  491. else
  492. {
  493. String symbolName;
  494. addr_target symbolOffset = 0;
  495. if ((!mDebugger->mDebugTarget->FindSymbolAt(addr, &symbolName, &symbolOffset, NULL, false)) || (symbolOffset > 64 * 1024))
  496. {
  497. auto dbgModule = mDebugger->mDebugTarget->FindDbgModuleForAddress(addr);
  498. if (dbgModule != NULL)
  499. symbolName += dbgModule->mDisplayName + "!";
  500. symbolName += StrFormat("0x%@", addr);
  501. }
  502. procId = Get(symbolName);
  503. }
  504. if (reverse)
  505. *(--procStackTraceHead) = procId;
  506. else
  507. procStackTraceHead[stackTraceProcIdx] = procId;
  508. stackTraceProcIdx++;
  509. if (subProgram == NULL)
  510. break;
  511. if (subProgram->mInlineeInfo == NULL)
  512. break;
  513. subProgram = subProgram->mInlineeInfo->mInlineParent;
  514. }
  515. }
  516. auto procEntry = AddToSet(mProfileProcEntrySet, procStackTraceHead, stackTraceProcIdx);
  517. if (procEntry->mEntryIdx == -1)
  518. {
  519. procEntry->mEntryIdx = (int)mProfileProcEntrySet.size() - 1; // Start at '0'
  520. }
  521. mProfileAddrToProcMap[addrEntry->mEntryIdx] = procEntry->mEntryIdx;
  522. }
  523. mPendingProfileEntries.Clear();
  524. }
  525. void DbgProfiler::Process()
  526. {
  527. AutoCrit autoCrit(mDebugger->mDebugManager->mCritSect);
  528. mNeedsProcessing = false;
  529. int time = mTotalActiveSamplingMS;
  530. BF_ASSERT(mProfileAddrEntries.IsEmpty());
  531. mProfileAddrEntries.Resize(mProfileAddrEntrySet.size() + 1);
  532. for (auto& val : mProfileAddrEntrySet)
  533. mProfileAddrEntries[val.mEntryIdx] = &val;
  534. BF_ASSERT(mProfileProcEntries.IsEmpty());
  535. mProfileProcEntries.Resize(mProfileProcEntrySet.size());
  536. for (auto& val : mProfileProcEntrySet)
  537. mProfileProcEntries[val.mEntryIdx] = &val;
  538. }
  539. String DbgProfiler::GetCallTree(int threadId, bool reverse)
  540. {
  541. if (mNeedsProcessing)
  542. Process();
  543. AutoCrit autoCrit(mDebugger->mDebugManager->mCritSect);
  544. BF_ASSERT(!mIsRunning);
  545. Array<ProfileProcEntry*> procEntries;
  546. String str;
  547. for (auto procEntry : mProfileProcEntries)
  548. {
  549. procEntry->mUsed = false;
  550. procEntry->mSampleCount = 0;
  551. }
  552. bool showIdleEntries = false;
  553. int totalSampleCount = 0;
  554. int totalActiveSampleCount = 0;
  555. for (auto& threadPair : mThreadInfo)
  556. {
  557. if ((threadId == 0) || (threadId == threadPair.mKey))
  558. {
  559. auto threadInfo = threadPair.mValue;
  560. totalSampleCount += threadInfo->mTotalSamples;
  561. for (auto addrEntryIdx : threadInfo->mProfileAddrEntries)
  562. {
  563. if (addrEntryIdx < 0)
  564. {
  565. if (showIdleEntries)
  566. addrEntryIdx = -addrEntryIdx;
  567. else
  568. continue;
  569. }
  570. int procEntryIdx = mProfileAddrToProcMap[addrEntryIdx];
  571. auto procEntry = mProfileProcEntries[procEntryIdx];
  572. if (procEntry->mSampleCount == 0)
  573. procEntries.push_back(procEntry);
  574. procEntry->mSampleCount++;
  575. totalActiveSampleCount++;
  576. }
  577. }
  578. }
  579. if (threadId == 0)
  580. str += "All Threads";
  581. else
  582. str += StrFormat("Thread %d", threadId);
  583. str += StrFormat("\t0\t%d\n", totalSampleCount);
  584. int idleTicks = totalSampleCount - totalActiveSampleCount;
  585. if (idleTicks != 0)
  586. str += StrFormat("<Idle>\t%d\t0\n-\n", idleTicks);
  587. AddEntries(str, procEntries, 0, (int)procEntries.size(), -1, NULL);
  588. str += "-\n";
  589. //BF_ASSERT(childSampleCount == totalSampleCount);
  590. return str;
  591. }