/* (C) 2003-2007 Willem Jan Hengeveld * Web: http://www.xs4all.nl/~itsme/ * http://wiki.xda-developers.com/ * * $Id$ * * module maskbit = 1<<((base>>25)-1) * * perldo s/\w+/sprintf("%08lx %s", 1<<((hex($&)>>25)-1), $&)/e * * slot 0 base 0x00000000 : current process * slot 1 base 0x02000000 : dlls * slot 2 base 0x04000000 -> maskbit 0x00000002 - filesys.exe * slot 3 base 0x06000000 -> maskbit 0x00000004 - device.exe * .. * slot 32 base 0x40000000 -> maskbit 0x80000000 * * slot ** base 0xc2000000 -> maskbit 0x00000001 kernel * * todo: * pps -n procname ... DONE * should do the same as 'pps -h HANDLE' * * pps -t -h HANDLE * pps -m -h HANDLE ... DONE * should list info only for proc HANDLE * * todo: add sorting options: * -Op = sort by cpu usage * -Oh = sort by heap usage * -On = sort by processname * -Ov = sort by slotnr * * * todo: add option to continue measuring * */ #include #include #include #include #include #define MEASUREMENT_MSEC 200 #include "itsutils.h" #include "dllversion.h" #include "debug.h" #include "ptrutils.h" #include "args.h" #include "util/HiresTimer.h" #include "util/timeconvert.h" class ProcessTimes { public: DWORD nThreads; DWORD tKernel; DWORD tUser; }; typedef std::map ProcessTimeMap; typedef std::map ProcessInfoMap; typedef std::map ThreadSummaryInfoMap; void PrintProcessList(const ProcessInfoMap &pinfo, const ProcessTimeMap &ptm0, const ProcessTimeMap &ptm1, DWORD measurementtime, bool bVerbose, bool bContinuous, HANDLE hProcFilter); bool GetProcessInfo(bool bIncludeHeap, ProcessInfoMap &pinfo); bool GetProcessTimes(ProcessTimeMap &ptimes, DWORD &tQuery); bool ITGetThreadUsageList(bool resolve_modulenames, DWORD& tQuery, ThreadSummaryInfoMap& tinfo); HANDLE ITGetProcessHandle(const std::string& szProcessName); void PrintThreadList(const ProcessInfoMap &pinfo, const ThreadSummaryInfoMap& tsim0, const ThreadSummaryInfoMap& tsim1, DWORD measurementtime, bool bVerbose, HANDLE hProcFilter); void PrintThreadEntry(const CEPROCESSENTRY* pe, const ThreadSummaryInfo& ti, const ThreadSummaryInfo* ti0, const ThreadSummaryInfo* ti1, DWORD measurementtime, bool bVerbose); void PrintContext(); void PrintThreadInfo(HANDLE hThread, DWORD tMeasure); bool ITGetThreadTimes(HANDLE hThread, FILETIME *tCreate, FILETIME *tExit, FILETIME *tKernel, FILETIME *tUser); int64_t CalcFiletimeDiff(const FILETIME *tFirst, const FILETIME *tLast); DWORD CalcPromile(int64_t a, int64_t b); DWORD GetProcessSectionSlot(HANDLE hProc); typedef std::vector ModuleEntryList; bool ITGetModuleList(bool bDirectRead, ModuleEntryList &pinfo); void PrintModuleList(bool bVerbose, const ModuleEntryList& modlist, DWORD dwProcMask); void write_timestamp() { debugt("\n"); } void usage() { printf("(C) 2003-2008 Willem jan Hengeveld itsme@xs4all.nl\n"); printf("Usage: pps [options]\n"); printf(" -t : list detailed thread info\n"); printf(" -s msec : measurement interval\n"); printf(" -s0 : give total cpu time, instead of percentage\n"); printf(" -m : list all modules ( or for process )\n"); printf(" -p HANDLE : restrict modules list to process\n"); printf(" -n PROCNAM: restrict modules list to process\n"); printf(" -c : continuously update\n"); printf(" -v : verbose\n"); } int main( int argc, char *argv[]) { DebugStdOut(); bool bVerbose= false; HANDLE hThread= INVALID_HANDLE_VALUE; HANDLE hProcess= INVALID_HANDLE_VALUE; char *szProcessName= NULL; DWORD tMeasure= MEASUREMENT_MSEC; bool bContinuous= false; bool bListAllThreads= false; bool bIncludeHeap= false; bool bListModules= false; int argsfound=0; for (int i=1 ; i>25)-1); } } if (bListModules) { // ! abusing -h = bIncludeHeap as alternate mode for modlist ModuleEntryList modlist; if (!ITGetModuleList(dwProcMask==0xFFFFFFFF ? bVerbose : true, modlist)) return 1; PrintModuleList(bVerbose, modlist, dwProcMask); return 0; } if (bListAllThreads) { ProcessInfoMap pinfo; ThreadSummaryInfoMap tsim0, tsim1; DWORD tQuery0, tQuery1; if (!GetProcessInfo(false, pinfo)) return 1; if (!ITGetThreadUsageList(false, tQuery0, tsim0)) return 1; if (tMeasure) { do { HiresTimer::usleep(1000LL*tMeasure); if (bContinuous) write_timestamp(); if (!ITGetThreadUsageList(true, tQuery1, tsim1)) return 1; PrintThreadList(pinfo, tsim0, tsim1, tQuery1-tQuery0, bVerbose, hProcess); } while (bContinuous); } else { PrintThreadList(pinfo, tsim0, tsim1, 0, bVerbose, hProcess); } } else if (hThread!=INVALID_HANDLE_VALUE) PrintThreadInfo(hThread, tMeasure); else { ProcessInfoMap pinfo; ProcessTimeMap tm0, tm1; DWORD tQuery0, tQuery1; if (!GetProcessInfo(bIncludeHeap, pinfo)) return 1; if (!GetProcessTimes(tm0, tQuery0)) return 1; if (tMeasure) { tm1.swap(tm0); do { HiresTimer::usleep(1000LL*tMeasure); if (bContinuous) { write_timestamp(); if (bVerbose) PrintContext(); } tm0.swap(tm1); if (!GetProcessTimes(tm1, tQuery1)) return 1; PrintProcessList(pinfo, tm0, tm1, tQuery1-tQuery0, bVerbose, bContinuous, hProcess); if (bContinuous) { // update pinfo too if (!GetProcessInfo(bIncludeHeap, pinfo)) return 1; } } while (bContinuous); } else { PrintProcessList(pinfo, tm0, tm1, 0, bVerbose, bContinuous, hProcess); } } StopItsutils(); return 0; } void PrintContext() { DWORD outsize=0; GetContextResult *outbuf=NULL; HRESULT res= ItsutilsInvoke("ITGetContext", 0, NULL, &outsize, (BYTE**)&outbuf); //printf("result=%08lx buf=%08lx size=%08lx\n", res, outbuf, outsize); if (res==0 && outbuf!=NULL) { verify_size(outbuf, outbuf->wszCmdLine+stringlength(outbuf->wszCmdLine)+1, outsize, "ITGetContext"); printf("pid=%08lx ph=%08lx tid=%08lx th=%08lx trust.caller=%d trust.cur=%d\n", outbuf->dwProcessId, outbuf->hProcess, outbuf->dwThreadId, outbuf->hThread, outbuf->dwCurrentTrust, outbuf->dwCallerTrust); printf("cmd: %ls\n", outbuf->wszCmdLine); printf("memory: kernel=%10d used=%10d free=%10d\n", outbuf->dwKernelMemory, outbuf->dwMemoryUsed, outbuf->dwMemoryFree); RapiFree(outbuf); } printf("\n"); } const ProcessTimes *GetTime(const ProcessTimeMap &tmap, HANDLE h) { ProcessTimeMap::const_iterator i= tmap.find(h); if (i==tmap.end()) return NULL; return &(*i).second; } double percentage(DWORD t0, DWORD t1, DWORD tmeasure) { if (t0>t1) return (t0-t1)*-100.0/tmeasure; else return (t1-t0)*100.0/tmeasure; } void PrintProcessList(const ProcessInfoMap &pinfo, const ProcessTimeMap &tm0, const ProcessTimeMap &tm1, DWORD measurementtime, bool bVerbose, bool bContinuous, HANDLE hProcFilter) { double pmUserTotal=0; double pmKernelTotal=0; DWORD dwMemTotal=0; DWORD dwTotalThreads=0; if (hProcFilter==INVALID_HANDLE_VALUE) { if (measurementtime==0) { // '-s0' option if (bVerbose) printf("%-8s %3s %-8s %-8s %10s %10s %7s %s\n", "handle", "n", "base", "bit", "kern", "user", "heap", "exe"); else printf("%-8s %3s %-8s %10s %10s %7s %s\n", "handle", "n", "base", "kern", "user", "heap", "exe"); } else { if (bVerbose) printf("%-8s %3s %-8s %-8s %5s %5s %7s %s\n", "handle", "n", "base", "bit", "kern", "user", "heap", "exe"); else printf("%-8s %3s %-8s %5s %5s %7s %s\n", "handle", "n", "base", "kern", "user", "heap", "exe"); } } for (ProcessInfoMap::const_iterator i=pinfo.begin() ; i!=pinfo.end() ; ++i) { HANDLE h= (*i).first; const CEPROCESSENTRY &pe= (*i).second; const ProcessTimes *pt0= GetTime(tm0,h); const ProcessTimes *pt1= GetTime(tm1,h); double pmUser = -1; double pmKernel= -1; if (hProcFilter!=INVALID_HANDLE_VALUE && h!=hProcFilter) continue; if (pt0 && pt1) { // NOTE: due to unknown reasons, sometimes the second tUser is smaller than the first tUser // i assume this is a kernel bug, since tUser should be the accumulated process time. // // -> this happens when threads are created/destroyed between // measurements. // pmUser= percentage(pt0->tUser, pt1->tUser, measurementtime); if (pmUser>0) pmUserTotal += pmUser; pmKernel= percentage(pt0->tKernel, pt1->tKernel, measurementtime); if (pmKernel>0) pmKernelTotal += pmKernel; if (bVerbose) printf("%08lx %3d %08lx %08lx %5.1f %5.1f %8d %ls%hs%ls\n", h, pt1->nThreads, pe.dwMemoryBase, 1<<((pe.dwMemoryBase>>25)-1), pmKernel, pmUser, pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); else printf("%08lx %3d %08lx %5.1f %5.1f %8d %ls%hs%ls\n", h, pt1->nThreads, pe.dwMemoryBase, pmKernel, pmUser, pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); dwTotalThreads += pt1->nThreads; } else if (bContinuous) { dwMemTotal -= pe.dwMemoryUsage; // process termninated // if (bVerbose) // printf("%08lx --- -------- -------- ----- ----- %ls%hs%ls\n", h, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); // else // printf("%08lx --- -------- ----- ----- %ls%hs%ls\n", h, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); } else if (pt0) { // absolute cpu time if (bVerbose) printf("%08lx %3d %08lx %08lx %10d %10d %8d %ls%hs%ls\n", h, pt0->nThreads, pe.dwMemoryBase, 1<<((pe.dwMemoryBase>>25)-1), pt0->tKernel, pt0->tUser, pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); else printf("%08lx %3d %08lx %10d %10d %8d %ls%hs%ls\n", h, pt0->nThreads, pe.dwMemoryBase, pt0->tKernel, pt0->tUser, pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); dwTotalThreads += pt0->nThreads; } else { // no timing info available if (bVerbose) printf("%08lx --- %08lx %08lx %8d %ls%hs%ls\n", h, pe.dwMemoryBase, 1<<((pe.dwMemoryBase>>25)-1), pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); else printf("%08lx --- %08lx %8d %ls%hs%ls\n", h, pe.dwMemoryBase, pe.dwMemoryUsage, pe.szExeFile, pe.szCmdLine[0]?" ":"", pe.szCmdLine); } dwMemTotal += pe.dwMemoryUsage; } if (hProcFilter==INVALID_HANDLE_VALUE) { if (measurementtime==0) { if (bVerbose) printf(" %2d %3d ........ %10s %10s %8d total\n", pinfo.size(), dwTotalThreads, "", "", dwMemTotal); else printf(" %2d %3d ........ %10s %10s %8d total\n", pinfo.size(), dwTotalThreads, "", "", dwMemTotal); } else { if (bVerbose) printf(" %2d %3d ........ %5.1f %5.1f %8d total\n", pinfo.size(), dwTotalThreads, pmKernelTotal, pmUserTotal, dwMemTotal); else printf(" %2d %3d ........ %5.1f %5.1f %8d total\n", pinfo.size(), dwTotalThreads, pmKernelTotal, pmUserTotal, dwMemTotal); } } } bool GetProcessInfo(bool bIncludeHeap, ProcessInfoMap &pinfo) { GetProcessListParams p; p.bIncludeHeapUsage= bIncludeHeap; DWORD outsize=0; GetProcessListResult *outbuf=NULL; HRESULT res= ItsutilsInvoke("ITGetProcessList", sizeof(GetProcessListParams), (BYTE*)&p, &outsize, (BYTE**)&outbuf); if (res || outbuf==NULL) { error(res, "ITGetProcessList"); return false; } verify_size(outbuf, outbuf->pe+outbuf->nEntries, outsize, "ITGetProcessList"); for (int i=0 ; inEntries ; i++) { memcpy(&pinfo[(HANDLE)outbuf->pe[i].dwProcessID], &outbuf->pe[i], sizeof(CEPROCESSENTRY)); } RapiFree(outbuf); return true; } // todo: make this more efficient. DWORD GetProcessSectionSlot(HANDLE hProc) { ProcessInfoMap pmap; if (GetProcessInfo(false, pmap)) if (pmap.find(hProc)!=pmap.end()) return pmap[hProc].dwMemoryBase; return 0; } bool ITGetModuleList(bool bDirectRead, ModuleEntryList &modlist) { DWORD outsize=0; GetModuleListResult *outbuf=NULL; GetModuleListParams p; p.bDirectRead= bDirectRead; HRESULT res= ItsutilsInvoke("ITGetModuleList", sizeof(GetModuleListParams), (BYTE*)&p, &outsize, (BYTE**)&outbuf); if (res || outbuf==NULL) { error(res, "ITGetModuleList"); return false; } verify_size(outbuf, outbuf->me+outbuf->nEntries, outsize, "ITGetModuleList"); modlist.resize(outbuf->nEntries); memcpy(vectorptr(modlist), outbuf->me, sizeof(CEMODULEENTRY)*outbuf->nEntries); RapiFree(outbuf); return true; } bool GetProcessTimes(ProcessTimeMap &ptimes, DWORD& tQuery) { DWORD outsize=0; GetProcessUsageListResult *outbuf=NULL; HRESULT res= ItsutilsInvoke("ITGetProcessUsageList", 0, NULL, &outsize, (BYTE**)&outbuf); if (res || outbuf==NULL) { error(res, "ITGetProcessUsageList"); return false; } verify_size(outbuf, outbuf->list+outbuf->nEntries, outsize, "ITGetProcessUsageList"); ptimes.clear(); for (int i=0 ; inEntries ; i++) { if (outbuf->list[i].hProc) { ptimes[outbuf->list[i].hProc].tKernel= outbuf->list[i].tKernel; ptimes[outbuf->list[i].hProc].tUser= outbuf->list[i].tUser; ptimes[outbuf->list[i].hProc].nThreads= outbuf->list[i].nThreads; } } tQuery= outbuf->tQuery; RapiFree(outbuf); return true; } void PrintThreadInfo(HANDLE hThread, DWORD tMeasure) { FILETIME tKernel0, tUser0; FILETIME tKernel1, tUser1; HiresTimer t0; if (!ITGetThreadTimes(hThread, NULL, NULL, &tKernel0, &tUser0)) return; if (tMeasure) { HiresTimer::usleep(1000LL*tMeasure); uint32_t t= t0.elapsed(); if (!ITGetThreadTimes(hThread, NULL, NULL, &tKernel1, &tUser1)) return; int64_t tKernel= CalcFiletimeDiff(&tKernel0, &tKernel1); int64_t tUser= CalcFiletimeDiff(&tUser0, &tUser1); printf("kernel = %4d user = %4d\n", CalcPromile(tKernel, t), CalcPromile(tUser, t)); } else { printf("kernel = %10lld user = %10lld\n", FiletimeToInt64(&tKernel0), FiletimeToInt64(&tUser0)); } } bool ITGetThreadTimes(HANDLE hThread, FILETIME *tCreate, FILETIME *tExit, FILETIME *tKernel, FILETIME *tUser) { DWORD outsize=0; GetThreadTimesResult *outbuf=NULL; GetThreadTimesParams p; p.hThread= hThread; HRESULT res= ItsutilsInvoke("ITGetThreadTimes", sizeof(p), (BYTE*)&p, &outsize, (BYTE**)&outbuf); if (res || outbuf==NULL) { error(res, "ITGetThreadTimes"); return false; } verify_size(outbuf, outbuf+1, outsize, "ITGetThreadTimes"); if (tCreate) *tCreate= outbuf->tCreate; if (tExit) *tExit= outbuf->tExit; if (tKernel) *tKernel= outbuf->tKernel; if (tUser) *tUser= outbuf->tUser; RapiFree(outbuf); return true; } int64_t CalcFiletimeDiff(const FILETIME *tFirst, const FILETIME *tLast) { return FiletimeToInt64(tLast)- FiletimeToInt64(tFirst); } DWORD CalcPromile(int64_t a, int64_t b) { if (b==0) return (DWORD)-1; a *= 1000; a /= b; return (DWORD)a; } bool ITGetThreadUsageList(bool resolve_modulenames, DWORD& tQuery, ThreadSummaryInfoMap& tinfo) { DWORD outsize=0; GetThreadUsageListResult *outbuf=NULL; GetThreadUsageListParams in; in.resolve_modulenames = resolve_modulenames; HRESULT res= ItsutilsInvoke("ITGetThreadUsageList", sizeof(in), (BYTE*)&in, &outsize, (BYTE**)&outbuf); if (res || outbuf==NULL) { error(res, "ITGetThreadUsageList"); return false; } verify_size(outbuf, outbuf->list+outbuf->nThreads, outsize, "ITGetThreadUsageList"); tinfo.clear(); for (DWORD i=0 ; inThreads ; i++) tinfo[outbuf->list[i].hThread]= outbuf->list[i]; tQuery= outbuf->tQuery; RapiFree(outbuf); return true; } void PrintThreadEntry(const CEPROCESSENTRY* pe, const ThreadSummaryInfo& ti, const ThreadSummaryInfo* ti0, const ThreadSummaryInfo* ti1, DWORD measurementtime, bool bVerbose) { printf("%08lx %08lx %02x %02x", ti.hThread, ti.dwStartAddr, ti.baseprio, ti.curprio); if (bVerbose) printf(" %08lx %08lx %08lx", ti.dwCurPC, ti.dwCurLR, ti.dwCurSP); if (measurementtime==0 && ti0) { // absolute time printf(" %10d %10d", ti0->tKernel, ti0->tUser); } else if (ti1==NULL && ti0==NULL) printf(" "); else if (ti1==NULL) printf(" terminated "); else if (ti0==NULL) printf(" created "); else if (measurementtime) { double pmUser= percentage(ti0->tUser,ti1->tUser, measurementtime); double pmKernel= percentage(ti0->tKernel,ti1->tKernel, measurementtime); printf(" %5.1f %5.1f", pmKernel, pmUser); } else { printf("ERROR"); } if (ti.szModname[0]) printf(" %-12ls", ti.szModname); else printf(" "); printf(" %08lx", ti.hProc); if (pe) printf(": %ls%hs%ls\n", pe->szExeFile, pe->szCmdLine[0]?" ":"", pe->szCmdLine); else printf(": ... unknown process\n"); } const CEPROCESSENTRY* getmapentry(const ProcessInfoMap& map, HANDLE key) { ProcessInfoMap::const_iterator i= map.find(key); if (i==map.end()) return NULL; return &(*i).second; } void PrintThreadList(const ProcessInfoMap &pinfo, const ThreadSummaryInfoMap& tsim0, const ThreadSummaryInfoMap& tsim1, DWORD measurementtime, bool bVerbose, HANDLE hProcFilter) { for (ThreadSummaryInfoMap::const_iterator i=tsim0.begin() ; i!=tsim0.end() ; ++i) { const ThreadSummaryInfo& ti0= (*i).second; if (hProcFilter!=INVALID_HANDLE_VALUE && ti0.hProc!=hProcFilter) continue; const CEPROCESSENTRY *pe= getmapentry(pinfo,ti0.hProc); ThreadSummaryInfoMap::const_iterator i1= tsim1.find((*i).first); if (tsim1.empty()) { PrintThreadEntry(pe, ti0, &ti0, NULL, 0, bVerbose); } else if (i1==tsim1.end()) { PrintThreadEntry(pe, ti0, &ti0, NULL, measurementtime, bVerbose); } else { const ThreadSummaryInfo& ti1= (*i1).second; PrintThreadEntry(pe, ti1, &ti0, &ti1, measurementtime, bVerbose); } } for (ThreadSummaryInfoMap::const_iterator i=tsim1.begin() ; i!=tsim1.end() ; ++i) { const ThreadSummaryInfo& ti1= (*i).second; if (hProcFilter!=INVALID_HANDLE_VALUE && ti1.hProc!=hProcFilter) continue; ThreadSummaryInfoMap::const_iterator i0= tsim0.find((*i).first); const CEPROCESSENTRY *pe= getmapentry(pinfo,ti1.hProc); if (i0==tsim0.end()) PrintThreadEntry(pe, ti1, NULL, &ti1, measurementtime, bVerbose); } } void PrintModuleList(bool bVerbose, const ModuleEntryList& modlist, DWORD dwProcMask) { if (bVerbose) printf(" membase vbase dbase usage name\n"); else printf(" membase name\n"); for (size_t i=0 ; i