Created
April 22, 2017 15:07
-
-
Save strictlymike/2176540fbfc3e688d539f8077b6f2e53 to your computer and use it in GitHub Desktop.
Automated performance monitoring diagnostics trigger
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @file | |
* High CPU utilization in a particular process. Herein, Single-CPU | |
* %utilization for both threads and processes is calculated as: | |
* | |
* 100 * kernel + user | |
* %u1 = ------------------- | |
* elapsed | |
* | |
* For multi-threaded applications, this figure can exceed 100% with regard to | |
* a process. Whole-system CPU %utilization is calculated as: | |
* | |
* %uALL = %u1 / nCpus | |
* | |
* This latter figure is the process-wide CPU utilization statistic depicted by | |
* Task Manager on a multi-CPU system. | |
*/ | |
#include <windows.h> | |
#include <stdio.h> | |
/* Tweak away as desired - can move these to getopt() later */ | |
#define INTERVAL_SEC 1 | |
#define THRESH_PCT 85 | |
#define THRESH_SEC 5 | |
/* Leave these alone */ | |
#define PID_INVALID -1 | |
#define INTERVAL_MSEC (1000*INTERVAL_SEC) | |
#define USEC_PER_SEC 1000000 | |
/* https://msdn.microsoft.com/en-us/library/windows/desktop/ms683223(v=vs.85).aspx */ | |
#define FILETIME_PER_SEC 10000000 | |
enum ProcTimeType | |
{ | |
pttUser, | |
pttKernel, | |
pttAll, | |
}; | |
BOOL MonitorByPid(int pid); | |
BOOL MonitorByHandle(HANDLE hProc, int pid); | |
LARGE_INTEGER GetProcTime(HANDLE hProc, enum ProcTimeType t); | |
BOOL GlobalsInit(struct watch_globals *g); | |
struct watch_globals | |
{ | |
LARGE_INTEGER freq; | |
DWORD ncpu; | |
double allcpus_thresh_pct; | |
}; | |
struct watch_globals gbls; | |
int | |
main(int argc, char **argv) | |
{ | |
BOOL Ok; | |
int ret = 0; | |
int pid = PID_INVALID; | |
if (2 == argc) | |
{ | |
pid = atoi(argv[1]); | |
if (0 == pid) | |
{ | |
pid = PID_INVALID; | |
} | |
} | |
if (PID_INVALID != pid) | |
{ | |
Ok = GlobalsInit(&gbls); | |
if (!Ok) | |
{ | |
return 1; | |
} | |
if (TRUE != MonitorByPid(pid)) | |
{ | |
fprintf(stderr, "Failed to monitor pid %d\n", pid); | |
ret = 1; | |
} | |
} | |
else | |
{ | |
ret = Usage(argv[0], stderr, 1); | |
} | |
return ret; | |
} | |
int | |
Usage(char *progname, FILE *out, int ret) | |
{ | |
fprintf(out, "Usage: %s <pid>\n", progname); | |
fprintf(out, "\n"); | |
fprintf(out, "Where pid is the pid of the process to monitor\n"); | |
return ret; | |
} | |
BOOL | |
GlobalsInit(struct watch_globals *g) | |
{ | |
SYSTEM_INFO si; | |
BOOL Ok; | |
GetSystemInfo(&si); | |
g->ncpu = si.dwNumberOfProcessors; | |
g->allcpus_thresh_pct = (double)THRESH_PCT / g->ncpu; | |
Ok = QueryPerformanceFrequency(&g->freq); | |
if (!Ok) | |
{ | |
fprintf(stderr, "QueryPerformanceFrequency failed, GLE=%d\n", | |
GetLastError()); | |
} | |
return Ok; | |
} | |
BOOL | |
MonitorByPid(int pid) | |
{ | |
BOOL Ret = FALSE; | |
HANDLE hProc = NULL; | |
/* The rights used below are the minimum requirement for | |
* EnumProcessModules() which is used by the diagnostic inspection program | |
* that is ultimately to be launched by this trigger. There is no reason to | |
* attempt to monitor a process that we do not have the requisite access to | |
* diagnose. We do not open the handle with PROCESS_ALL_ACCESS in favor of | |
* applying the principle of least privilege. */ | |
hProc = OpenProcess( | |
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, | |
FALSE, | |
pid | |
); | |
if (NULL == hProc) | |
{ | |
fprintf(stderr, "OpenProcess failed, GLE=%d\n", GetLastError()); | |
} | |
else | |
{ | |
Ret = MonitorByHandle(hProc, pid); | |
CloseHandle(hProc); | |
} | |
return Ret; | |
} | |
BOOL | |
MonitorByHandle(HANDLE hProc, int pid) | |
{ | |
BOOL Ok; | |
LARGE_INTEGER t1us, t2us; | |
LARGE_INTEGER used1; | |
LARGE_INTEGER used2; | |
double sec_used; | |
double sec_elapsed; | |
double pct; | |
printf("Monitoring process %d, press ^C to exit\n", pid); | |
for (;;) | |
{ | |
/* Get first timestamp and process times. Note, rdstsc/rdtscp compiler | |
* intrinsic is not used here because doing so would require attention | |
* to several details that QPC already takes into account (which | |
* processor was this collected from, etc.), except on machines with | |
* broken BIOS implementations. For more information, see: | |
* https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx | |
*/ | |
Ok = QueryPerformanceCounter(&t1us); | |
if (!Ok) { break; } | |
used1 = GetProcTime(hProc, pttAll); | |
if (used1.QuadPart == 0) { break; } | |
/* sleep xxx */ | |
Sleep(INTERVAL_MSEC); | |
/* Get second timestamp and process times. */ | |
Ok = QueryPerformanceCounter(&t2us); | |
if (!Ok) { break; } | |
used2 = GetProcTime(hProc, pttAll); | |
if (used2.QuadPart == 0) { break; } | |
/* Calculations */ | |
sec_used = (double)(used2.QuadPart - used1.QuadPart) / FILETIME_PER_SEC; | |
sec_elapsed = (double)(t2us.QuadPart - t1us.QuadPart) / gbls.freq.QuadPart; | |
printf("sec_used = %G\n", sec_used); | |
printf("sec_elap = %G\n", sec_elapsed); | |
/* %t = used1 / dtus */ | |
pct = 100 * sec_used / sec_elapsed; | |
printf("Single-CPU usage: %G%%\n", pct); | |
printf("Whole-sys CPU usage: %G%%\n", pct/gbls.ncpu); | |
} | |
if (!Ok) | |
{ | |
fprintf(stderr, "Error collecting counters\n"); | |
} | |
return Ok; | |
} | |
LARGE_INTEGER | |
GetProcTime(HANDLE hProc, enum ProcTimeType t) | |
{ | |
FILETIME ctimef; | |
FILETIME etimef; | |
FILETIME ktimef; | |
FILETIME utimef; | |
BOOL Ok; | |
LARGE_INTEGER rettime; | |
rettime.QuadPart = 0; | |
Ok = GetProcessTimes(hProc, &ctimef, &etimef, &ktimef, &utimef); | |
if (Ok) | |
{ | |
switch (t) | |
{ | |
case pttKernel: | |
rettime.LowPart = ktimef.dwLowDateTime; | |
rettime.HighPart = ktimef.dwHighDateTime; | |
break; | |
case pttUser: | |
rettime.LowPart = utimef.dwLowDateTime; | |
rettime.HighPart = utimef.dwHighDateTime; | |
break; | |
case pttAll: | |
rettime.LowPart = utimef.dwLowDateTime + ktimef.dwLowDateTime; | |
rettime.HighPart = utimef.dwHighDateTime + ktimef.dwHighDateTime; | |
break; | |
default: | |
rettime.QuadPart = 0; | |
} | |
} | |
else | |
{ | |
rettime.QuadPart = 0; | |
} | |
return rettime; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I didn't implement carry logic in the addition of user and kernel times. #exerciseleftforthestudent :-P