Skip to content

Instantly share code, notes, and snippets.

@strictlymike
Created April 22, 2017 15:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save strictlymike/2176540fbfc3e688d539f8077b6f2e53 to your computer and use it in GitHub Desktop.
Save strictlymike/2176540fbfc3e688d539f8077b6f2e53 to your computer and use it in GitHub Desktop.
Automated performance monitoring diagnostics trigger
/**
* @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;
}
@strictlymike
Copy link
Author

I didn't implement carry logic in the addition of user and kernel times. #exerciseleftforthestudent :-P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment