/*
* Author: SpringHack - springhack@live.cn
* Last modified: 2020-02-22 02:22:48
* Filename: cpu_usage_win.cc
* Description: Created by SpringHack using vim automatically.
*/
#include "cpu_usage.hpp"
#include <atlbase.h>
#include <windows.h>
#include <dbghelp.h>
#include <tlhelp32.h>
#pragma comment(lib, "dbghelp.lib")
#define NUMBER_OF_PROCESSORS (16)
#define PROCESSOR_BUFFER_SIZE (NUMBER_OF_PROCESSORS * 8)
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
typedef enum _THREADINFOCLASS {
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair_Reusable,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress, // Obsolete
ThreadIsIoPending,
ThreadHideFromDebugger,
ThreadBreakOnTermination,
ThreadSwitchLegacyState,
ThreadIsTerminated,
ThreadLastSystemCall,
ThreadIoPriority,
ThreadCycleTime,
ThreadPagePriority,
ThreadActualBasePriority,
ThreadTebInformation,
ThreadCSwitchMon,
ThreadCSwitchPmu,
ThreadWow64Context,
ThreadGroupInformation,
ThreadUmsInformation,
ThreadCounterProfiling,
ThreadIdealProcessorEx,
MaxThreadInfoClass
} THREADINFOCLASS;
typedef NTSTATUS (WINAPI *pNtQIT)(IN HANDLE ThreadHandle, IN THREADINFOCLASS ThreadInformationClass, OUT PVOID ThreadInformation, IN ULONG ThreadInformationLength, OUT PULONG ReturnLength);
typedef HRESULT (WINAPI *pSetTD)(HANDLE hThread, PCWSTR lpThreadDescription);
typedef HRESULT (WINAPI *pGetTD)(HANDLE hThread, PCWSTR *lpThreadDescription);
#define LOCK(X) std::lock_guard<std::mutex> s_cpu_usage_guard(X);
namespace cpu_usage {
system_info::system_info(double u, double s, double i)
: user_cpu_usage(u), idle_cpu_usage(i), system_cpu_usage(s) {}
thread_info::thread_info(std::string name, unsigned long int id, double cpu)
: thread_name(name), thread_id(id), cpu_usage(cpu) {}
bool counter_started = false;
bool thread_counter_started = false;
uint64_t counter_interval = 1000;
double _proc_cpu = 0.0f;
std::vector<thread_info> _thread_cpus;
system_info _sys_cpu(0, 0, 0);
std::thread g_counter_worker;
std::mutex g_cpu_usage_mutex;
void worker();
uint64_t get_interval() {
LOCK(g_cpu_usage_mutex)
return counter_interval;
}
void set_interval(uint64_t x) {
LOCK(g_cpu_usage_mutex)
counter_interval = x;
}
void start_counter() {
if (counter_started)
return;
{
LOCK(g_cpu_usage_mutex)
counter_started = true;
}
g_counter_worker = std::thread(worker);
g_counter_worker.detach();
}
void stop_counter() {
if (!counter_started)
return;
{
LOCK(g_cpu_usage_mutex)
counter_started = false;
}
}
void start_thread_counter() {
if (thread_counter_started) return;
LOCK(g_cpu_usage_mutex);
thread_counter_started = true;
}
void stop_thread_counter() {
if (!thread_counter_started) return;
LOCK(g_cpu_usage_mutex);
thread_counter_started = false;
}
system_info sys_cpu_usage() {
LOCK(g_cpu_usage_mutex)
return _sys_cpu;
}
double proc_cpu_usage() {
LOCK(g_cpu_usage_mutex)
return _proc_cpu;
}
std::vector<thread_info> thread_cpu_usage() {
LOCK(g_cpu_usage_mutex)
return _thread_cpus;
}
using namespace std;
// Private APIs
uint64_t _filetime_to_u64(FILETIME filetime) {
return (((uint64_t)filetime.dwHighDateTime << 32) | (uint64_t)filetime.dwLowDateTime) / 10;
}
void _get_sys_times(uint64_t* idle, uint64_t* kernel, uint64_t* user) {
FILETIME _idle, _kernel, _user;
GetSystemTimes(&_idle, &_kernel, &_user);
*idle = _filetime_to_u64(_idle);
*kernel = _filetime_to_u64(_kernel);
*user = _filetime_to_u64(_user);
}
bool _get_process_times(uint32_t pid, uint64_t* kernel, uint64_t* user) {
static HANDLE handler = OpenProcess(PROCESS_QUERY_INFORMATION, 0, pid);
if (!handler) return false;
FILETIME create_time, exit_time, kernel_time, user_time;
int ret = GetProcessTimes(handler, &create_time, &exit_time, &kernel_time, &user_time);
if (ret == 0) return false;
*kernel = _filetime_to_u64(kernel_time);
*user = _filetime_to_u64(user_time);
return true;
}
bool _get_thread_times(uint32_t tid, uint64_t* kernel, uint64_t* user) {
HANDLE handler = OpenThread(THREAD_QUERY_INFORMATION, 0, tid);
if (!handler) return false;
FILETIME create_time, exit_time, kernel_time, user_time;
int ret = GetThreadTimes(handler, &create_time, &exit_time, &kernel_time, &user_time);
CloseHandle(handler);
if (ret == 0) return false;
*kernel = _filetime_to_u64(kernel_time);
*user = _filetime_to_u64(user_time);
return true;
}
template <typename Func>
void _get_times(Func func, uint32_t id, uint64_t* pwork_time, uint64_t* total_time) {
uint64_t sys_idle, sys_kernel, sys_user;
_get_sys_times(&sys_idle, &sys_kernel, &sys_user);
uint64_t pro_kernel, pro_user;
func(id, &pro_kernel, &pro_user);
*pwork_time = pro_kernel + pro_user;
*total_time = sys_kernel + sys_user;
}
DWORD WINAPI _get_thread_start_ddress(HANDLE hThread) {
NTSTATUS ntStatus;
HANDLE hDupHandle;
DWORD dwStartAddress;
static pNtQIT NtQueryInformationThread = (pNtQIT)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationThread");
if (NtQueryInformationThread == NULL) return 0;
HANDLE hCurrentProcess = GetCurrentProcess();
if (!DuplicateHandle(hCurrentProcess, hThread, hCurrentProcess, &hDupHandle, THREAD_QUERY_INFORMATION, FALSE, 0)) {
SetLastError(ERROR_ACCESS_DENIED);
return 0;
}
ntStatus = NtQueryInformationThread(hDupHandle, ThreadQuerySetWin32StartAddress, &dwStartAddress, sizeof(DWORD), NULL);
CloseHandle(hDupHandle);
if (ntStatus != STATUS_SUCCESS) return 0;
return dwStartAddress;
}
string _get_symbol_name_by_address(DWORD dwAddress, uint64_t index) {
static HANDLE hProcess = GetCurrentProcess();
static BOOL inited = SymInitialize(hProcess, nullptr, TRUE);
DWORD64 dwDisplacement = 0;
char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
pSymbol->MaxNameLen = MAX_SYM_NAME;
if (SymFromAddr(hProcess, dwAddress, &dwDisplacement, pSymbol)) {
return string(pSymbol->Name) + "(" + to_string(index) + ")";
} else {
DWORD error = GetLastError();
return "";
}
}
string _w_to_a(PCWSTR w_str) {
USES_CONVERSION;
string retval(W2A(w_str));
return retval;
}
string _get_thread_description(HANDLE hThread, uint64_t index) {
static pGetTD _GetThreadDescription = (pGetTD)GetProcAddress(GetModuleHandle("kernel32.dll"), "GetThreadDescription");
if (_GetThreadDescription == NULL) return "";
PCWSTR name;
HRESULT hr = _GetThreadDescription(hThread, &name);
if (FAILED(hr)) return "";
string retval;
string argval = _w_to_a(name);
stringstream convertor;
convertor << argval;
convertor >> retval;
if (retval.empty()) return retval;
return retval + "(" + to_string(index) + ")";
}
vector<thread_info> _get_all_threads() {
vector<thread_info> threads;
uint64_t pid = GetCurrentProcessId();
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pid);
uint64_t index = 1;
if (h != INVALID_HANDLE_VALUE) {
THREADENTRY32 te;
te.dwSize = sizeof(te);
if (Thread32First(h, &te)) {
do {
if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) {
if (te.th32OwnerProcessID == pid) {
HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION, 0, te.th32ThreadID);
string thread_name = _get_thread_description(hThread, index);
if (thread_name.empty()) {
DWORD addr = _get_thread_start_ddress(hThread);
thread_name = _get_symbol_name_by_address(addr, index);
}
CloseHandle(hThread);
if (thread_name.empty()) {
thread_name = "thread " + to_string(index);
}
++index;
threads.push_back(thread_info(thread_name, te.th32ThreadID, 0));
}
}
te.dwSize = sizeof(te);
} while (Thread32Next(h, &te));
}
CloseHandle(h);
}
return threads;
}
void _get_sys_cpu(double& idle, double& user) {
static ULONG64 ProcessorIdleTimeBuffer[PROCESSOR_BUFFER_SIZE];
FILETIME IdleTime, KernelTime, UserTime;
static unsigned long long PrevTotal = 0;
static unsigned long long PrevIdle = 0;
static unsigned long long PrevUser = 0;
unsigned long long ThisTotal;
unsigned long long ThisIdle, ThisKernel, ThisUser;
unsigned long long TotalSinceLast, IdleSinceLast, UserSinceLast;
GetSystemTimes(&IdleTime, &KernelTime, &UserTime);
ThisIdle = _filetime_to_u64(IdleTime);
ThisKernel = _filetime_to_u64(KernelTime);
ThisUser = _filetime_to_u64(UserTime);
ThisTotal = ThisKernel + ThisUser;
TotalSinceLast = ThisTotal - PrevTotal;
IdleSinceLast = ThisIdle - PrevIdle;
UserSinceLast = ThisUser - PrevUser;
double Headroom;
Headroom = (double)IdleSinceLast / (double)TotalSinceLast;
double Load;
Load = 1.0 - Headroom;
Headroom *= 100.0; // to make it percent
Load *= 100.0; // percent
PrevTotal = ThisTotal;
PrevIdle = ThisIdle;
PrevUser = ThisUser;
idle = Headroom;
user = Load;
}
void worker() {
bool continue_work = true;
bool _thread_counter_started = thread_counter_started;
uint64_t p_p_work = 0, p_p_total = 0;
map<uint64_t, uint64_t> p_tid_work;
map<uint64_t, uint64_t> p_tid_total;
static pSetTD _SetThreadDescription = (pSetTD)GetProcAddress(GetModuleHandle("kernel32.dll"), "SetThreadDescription");
if (_SetThreadDescription != NULL) {
_SetThreadDescription(GetCurrentThread(), L"ele_ex_cpu_counter");
}
while (continue_work) {
// sys cpu
double idle = 0.0f, user = 0.0f;
_get_sys_cpu(idle, user);
system_info tmp_sys_cpu(user, 0, idle);
// process cpu
uint64_t p_work = 0, p_total = 0;
double tmp_proc_cpu = 0.0f;
_get_times(_get_process_times, GetCurrentProcessId(), &p_work, &p_total);
uint64_t dt_work = p_work - p_p_work;
uint64_t dt_total = p_total - p_p_total;
if (dt_total != 0) {
tmp_proc_cpu = (double)dt_work * 100 / (double)dt_total;
}
p_p_work = p_work;
p_p_total = p_total;
// threads
if (_thread_counter_started) {
vector<thread_info> tmp_thread_cpus = _get_all_threads();
map<uint64_t, uint64_t> tid_work;
map<uint64_t, uint64_t> tid_total;
for (thread_info &thread: tmp_thread_cpus) {
uint64_t tid = thread.thread_id;
uint64_t work = 0, total = 0;
_get_times(_get_thread_times, tid, &work, &total);
uint64_t dt_work = work;
uint64_t dt_total = total;
if (p_tid_work[tid]) dt_work -= p_tid_work[tid];
if (p_tid_total[tid]) dt_total -= p_tid_total[tid];
if (dt_total != 0) {
double thread_cpu = (double)dt_work * 100 / (double)dt_total;
thread.cpu_usage = thread_cpu;
}
tid_work[tid] = work;
tid_total[tid] = total;
}
p_tid_work = tid_work;
p_tid_total = tid_total;
LOCK(g_cpu_usage_mutex)
_thread_cpus = tmp_thread_cpus;
}
// lock and change global vars
{
LOCK(g_cpu_usage_mutex)
_sys_cpu = tmp_sys_cpu;
_proc_cpu = tmp_proc_cpu;
continue_work = counter_started;
_thread_counter_started = thread_counter_started;
}
cpu_time::cpu_time_counter(_proc_cpu);
Sleep(counter_interval);
}
}
}