/*
    PIP - Platform Independent Primitives
    Process resource monitor
    Copyright (C) 2016  Ivan Pelipenko peri4ko@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "pisystemmonitor.h"
#include "pisysteminfo.h"
#ifdef WINDOWS
#  include "tlhelp32.h"
#endif


PISystemMonitor::PISystemMonitor(): PIThread() {
	pID_ = cycle = 0;
	cpu_count = PISystemInfo::instance()->processorsCount;
#ifndef WINDOWS
#  ifdef QNX
	page_size = 4096;
#  else
	page_size = getpagesize();
#  endif
#else
	hProc = 0;
	mem_cnt.cb = sizeof(mem_cnt);
#endif
	setName("system_monitor");
}


PISystemMonitor::~PISystemMonitor() {
	stop();
}


PISystemMonitor::ProcessStats::ProcessStats() {
	ID = parent_ID = group_ID = session_ID = priority = threads = 0;
	physical_memsize = resident_memsize = share_memsize = virtual_memsize = data_memsize = 0;
	cpu_load_user = cpu_load_system = 0.f;
}


bool PISystemMonitor::startOnProcess(int pID) {
	stop();
	pID_ = pID;
#ifndef WINDOWS
	file.open("/proc/" + PIString::fromNumber(pID_) + "/stat", PIIODevice::ReadOnly);
	filem.open("/proc/" + PIString::fromNumber(pID_) + "/statm", PIIODevice::ReadOnly);
	if (!file.isOpened()) {
		piCoutObj << "Can`t find process with ID = " << pID_ << "!";
		return false;
	}
	cycle = -1;
#else
	hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pID_);
	if (hProc == 0) {
		piCoutObj << "Can`t open process with ID = " << pID_ << "," << errorString();
		return false;
	}
	tm.reset();
#endif
	return start(1000);
}


void PISystemMonitor::stop() {
	PIThread::stop();
#ifdef WINDOWS
	if (hProc != 0) {
		CloseHandle(hProc);
		hProc = 0;
	}
#endif

}


void PISystemMonitor::run() {
#ifndef WINDOWS
	file.seekToBegin();
	PIString str(file.readAll(true));
	int si = str.find('(') + 1, fi = 0, cc = 1;
	for (int i = si; i < str.size_s(); ++i) {
		if (str[i] == '(') cc++;
		if (str[i] == ')') cc--;
		if (cc <= 0) {
			fi = i;
			break;
		}
	}
	stat.exec_name = str.mid(si, fi - si);
	str.cutMid(si - 1, fi - si + 3);
	PIStringList sl = str.split(" ");
	if (sl.size_s() < 19) return;
	stat.ID = sl[0].toInt();
	stat.state = sl[1];
	stat.parent_ID = sl[2].toInt();
	stat.group_ID = sl[3].toInt();
	stat.session_ID = sl[4].toInt();
	if (cycle < 0) {
		cpu_u_prev = cpu_u_cur = sl[12].toLLong();
		cpu_s_prev = cpu_s_cur = sl[13].toLLong();
	}
	cycle++;
	//if (cycle >= 4) {
		cpu_u_prev = cpu_u_cur;
		cpu_s_prev = cpu_s_cur;
		cpu_u_cur = sl[12].toLLong();
		cpu_s_cur = sl[13].toLLong();
		stat.cpu_load_system = cpu_s_cur - cpu_s_prev;
		stat.cpu_load_user = cpu_u_cur - cpu_u_prev;
		stat.cpu_load_system /= cpu_count;
		stat.cpu_load_user /= cpu_count;
		cycle = 0;
	//}
	stat.priority = sl[16].toInt();
	stat.threads = sl[18].toInt();

	filem.seekToBegin();
	str = filem.readAll(true);
	sl = str.split(" ");
	if (sl.size_s() < 6) return;
	stat.virtual_memsize = sl[0].toLong() * page_size;
	stat.resident_memsize = sl[1].toLong() * page_size;
	stat.share_memsize = sl[2].toLong() * page_size;
	stat.data_memsize = sl[5].toLong() * page_size;
	stat.physical_memsize = stat.resident_memsize - stat.share_memsize;
#else
	stat.ID = pID_;
	HMODULE hMod;
	DWORD cbNeeded;
	if (GetProcessMemoryInfo(hProc, &mem_cnt, sizeof(mem_cnt)) != 0) {
		stat.physical_memsize = mem_cnt.WorkingSetSize;
	}
	stat.priority = GetPriorityClass(hProc);
	HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, pID_);
	int thcnt = 0;
	if (snap != 0) {
		THREADENTRY32 thread;
		thread.dwSize = sizeof(THREADENTRY32);
		if (Thread32First(snap, &thread) == TRUE) {
			if (thread.th32OwnerProcessID == pID_)
				++thcnt;
			while (Thread32Next(snap, &thread) == TRUE) {
				if (thread.th32OwnerProcessID == pID_)
					++thcnt;
			}
		}
		stat.threads = thcnt;
		CloseHandle(snap);
	}
	FILETIME ft0, ft1, ft_kernel, ft_user;
	double el_s = tm.elapsed_s() * cpu_count / 100.;
	if (GetProcessTimes(hProc, &ft0, &ft1, &ft_kernel, &ft_user) != 0) {
		PISystemTime tm_kernel_c(ft_kernel), tm_user_c(ft_user);
		if (cycle < 0) {
			tm_kernel = tm_kernel_c;
			tm_user = tm_user_c;
		}
		//cycle++;
		cycle = 0;
		if (el_s <= 0.) {
			stat.cpu_load_system = 0.f;
			stat.cpu_load_user = 0.f;
		} else {
			stat.cpu_load_system = (tm_kernel_c - tm_kernel).toSeconds() / el_s;
			stat.cpu_load_user = (tm_user_c - tm_user).toSeconds() / el_s;
		}
		tm_kernel = tm_kernel_c;
		tm_user = tm_user_c;
	} else {
		stat.cpu_load_system = 0.f;
		stat.cpu_load_user = 0.f;
	}
	tm.reset();
#endif
	
	stat.cpu_load_system = piClampf(stat.cpu_load_system, 0.f, 100.f);
	stat.cpu_load_user = piClampf(stat.cpu_load_user, 0.f, 100.f);
	makeStrings();
}


void PISystemMonitor::makeStrings() {
	stat.physical_memsize_readable.setReadableSize(stat.physical_memsize);
	stat.resident_memsize_readable.setReadableSize(stat.resident_memsize);
	stat.share_memsize_readable.setReadableSize(stat.share_memsize);
	stat.virtual_memsize_readable.setReadableSize(stat.virtual_memsize);
	stat.data_memsize_readable.setReadableSize(stat.data_memsize);
}
