Files
pip/libs/main/application/pisystemmonitor.cpp

461 lines
13 KiB
C++

/*
PIP - Platform Independent Primitives
Process resource monitor
Ivan Pelipenko peri4ko@yandex.ru
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pisystemmonitor.h"
#include "pidir.h"
#include "piliterals_string.h"
#include "piprocess.h"
#include "pisysteminfo.h"
#include "pitime_win.h"
#include "pitranslator.h"
#include <unistd.h>
#ifdef WINDOWS
# include <psapi.h>
# include <tlhelp32.h>
#endif
#ifdef MAC_OS
struct kqueue_id_t;
# include <libproc.h>
# include <sys/proc_info.h>
#endif
#ifdef ESP_PLATFORM
# include "esp_heap_caps.h"
#endif
void PISystemMonitor::ProcessStats::makeStrings() {
physical_memsize_readable.setReadableSize(physical_memsize);
resident_memsize_readable.setReadableSize(resident_memsize);
share_memsize_readable.setReadableSize(share_memsize);
virtual_memsize_readable.setReadableSize(virtual_memsize);
data_memsize_readable.setReadableSize(data_memsize);
}
#ifndef MICRO_PIP
PRIVATE_DEFINITION_START(PISystemMonitor)
# ifndef WINDOWS
# ifdef MAC_OS
PISystemTime
# else
llong
# endif
cpu_u_cur,
cpu_u_prev, cpu_s_cur, cpu_s_prev;
PIString proc_dir;
PIFile file, filem;
# else
HANDLE hProc;
PROCESS_MEMORY_COUNTERS mem_cnt;
PISystemTime tm_kernel, tm_user;
PITimeMeasurer tm;
# endif
PRIVATE_DEFINITION_END(PISystemMonitor)
#endif
PISystemMonitor::PISystemMonitor(): PIThread() {
pID_ = cycle = 0;
cpu_count = PISystemInfo::instance()->processorsCount;
#ifndef MICRO_PIP
# ifndef WINDOWS
# ifdef QNX
page_size = 4096;
# else
page_size = getpagesize();
# endif
# else
PRIVATE->hProc = 0;
PRIVATE->mem_cnt.cb = sizeof(PRIVATE->mem_cnt);
# endif
#endif
setName("system_monitor"_a);
}
PISystemMonitor::~PISystemMonitor() {
stop();
}
#ifndef MICRO_PIP
bool PISystemMonitor::startOnProcess(int pID, PISystemTime interval) {
stop();
pID_ = pID;
Pool::instance()->add(this);
cycle = -1;
# ifndef WINDOWS
# ifndef MAC_OS
PRIVATE->proc_dir = PIStringAscii("/proc/") + PIString::fromNumber(pID_) + PIStringAscii("/");
PRIVATE->file.open(PRIVATE->proc_dir + "stat", PIIODevice::ReadOnly);
PRIVATE->filem.open(PRIVATE->proc_dir + "statm", PIIODevice::ReadOnly);
if (!PRIVATE->file.isOpened()) {
piCoutObj << "Can`t find process with ID = %1!"_tr("PISystemMonitor").arg(pID_);
return false;
}
# endif
# else
PRIVATE->hProc = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pID_);
if (PRIVATE->hProc == 0) {
piCoutObj << "Can`t open process with ID = %1, %2!"_tr("PISystemMonitor").arg(pID_).arg(errorString());
return false;
}
PRIVATE->tm.reset();
# endif
return start(interval);
}
#endif
bool PISystemMonitor::startOnSelf(PISystemTime interval) {
#ifndef MICRO_PIP
bool ret = startOnProcess(PIProcess::currentPID(), interval);
cycle = -1;
#else
bool ret = start(interval);
#endif
return ret;
}
PIVector<PISystemMonitor::ThreadStats> PISystemMonitor::threadsStatistic() const {
lock();
PIVector<PISystemMonitor::ThreadStats> ret = cur_ts;
unlock();
return ret;
}
void PISystemMonitor::setStatistic(const PISystemMonitor::ProcessStats & s) {
PIMutexLocker _ml(stat_mutex);
stat = s;
stat.makeStrings();
}
void PISystemMonitor::stop() {
PIThread::stopAndWait();
#ifdef WINDOWS
if (PRIVATE->hProc != 0) {
CloseHandle(PRIVATE->hProc);
PRIVATE->hProc = 0;
}
#endif
Pool::instance()->remove(this);
}
PISystemMonitor::ProcessStats PISystemMonitor::statistic() const {
PIMutexLocker _ml(stat_mutex);
return stat;
}
#ifdef MAC_OS
PISystemTime uint64toST(uint64_t v) {
return PISystemTime(((uint *)&(v))[1], ((uint *)&(v))[0]);
}
#endif
void PISystemMonitor::run() {
cur_tm.clear();
tbid.clear();
__PIThreadCollection * pitc = __PIThreadCollection::instance();
pitc->lock();
PIVector<PIThread *> tv = pitc->threads();
for (auto * t: tv)
if (t->isPIObject()) tbid[t->tid()] = t->name();
pitc->unlock();
// piCout << tbid.keys().toType<uint>();
ProcessStats tstat;
tstat.ID = pID_;
#ifdef MICRO_PIP
for (auto * t: tv)
if (t->isPIObject()) gatherThread(t->tid());
#else
# ifndef WINDOWS
double delay_ms = delay_.toMilliseconds();
tbid[pID_] = "main";
# ifdef MAC_OS
rusage_info_current ru;
proc_pid_rusage(pID_, RUSAGE_INFO_CURRENT, (rusage_info_t *)&ru);
// piCout << PISystemTime(((uint*)&(ru.ri_user_time))[1], ((uint*)&(ru.ri_user_time))[0]);
if (cycle < 0) {
PRIVATE->cpu_u_prev = PRIVATE->cpu_u_cur = uint64toST(ru.ri_user_time);
PRIVATE->cpu_s_prev = PRIVATE->cpu_s_cur = uint64toST(ru.ri_system_time);
}
PRIVATE->cpu_u_prev = PRIVATE->cpu_u_cur;
PRIVATE->cpu_s_prev = PRIVATE->cpu_s_cur;
PRIVATE->cpu_u_cur = uint64toST(ru.ri_user_time);
PRIVATE->cpu_s_cur = uint64toST(ru.ri_system_time);
tstat.cpu_load_system = 100.f * (PRIVATE->cpu_s_cur - PRIVATE->cpu_s_prev).toMilliseconds() / delay_ms;
tstat.cpu_load_user = 100.f * (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev).toMilliseconds() / delay_ms;
cycle = 0;
// piCout << (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev).toMilliseconds() / delay_ms;
# else
PRIVATE->file.seekToBegin();
PIString str = PIString::fromAscii(PRIVATE->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;
}
}
tstat.exec_name = str.mid(si, fi - si);
str.cutMid(si - 1, fi - si + 3);
PIStringList sl = str.split(" ");
if (sl.size_s() < 19) return;
tstat.ID = sl[0].toInt();
tstat.state = sl[1];
tstat.parent_ID = sl[2].toInt();
tstat.group_ID = sl[3].toInt();
tstat.session_ID = sl[4].toInt();
if (cycle < 0) {
PRIVATE->cpu_u_prev = PRIVATE->cpu_u_cur = sl[12].toLLong();
PRIVATE->cpu_s_prev = PRIVATE->cpu_s_cur = sl[13].toLLong();
}
PRIVATE->cpu_u_prev = PRIVATE->cpu_u_cur;
PRIVATE->cpu_s_prev = PRIVATE->cpu_s_cur;
PRIVATE->cpu_u_cur = sl[12].toLLong();
PRIVATE->cpu_s_cur = sl[13].toLLong();
tstat.cpu_load_system = (PRIVATE->cpu_s_cur - PRIVATE->cpu_s_prev) / (delay_ms / 1000.);
tstat.cpu_load_user = (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev) / (delay_ms / 1000.);
tstat.cpu_load_system /= cpu_count;
tstat.cpu_load_user /= cpu_count;
cycle = 0;
tstat.priority = sl[16].toInt();
tstat.threads = sl[18].toInt();
// piCout << "\n";
// piCout << sl[0] << sl[12] << sl[13];
PRIVATE->filem.seekToBegin();
str = PIString::fromAscii(PRIVATE->filem.readAll(true));
sl = str.split(" ");
if (sl.size_s() < 6) return;
tstat.virtual_memsize = sl[0].toLong() * page_size;
tstat.resident_memsize = sl[1].toLong() * page_size;
tstat.share_memsize = sl[2].toLong() * page_size;
tstat.data_memsize = sl[5].toLong() * page_size;
tstat.physical_memsize = tstat.resident_memsize - tstat.share_memsize;
PIVector<PIFile::FileInfo> tld = PIDir(PRIVATE->proc_dir + "task").entries();
for (const auto & i: tld) {
if (i.flags[PIFile::FileInfo::Dot] || i.flags[PIFile::FileInfo::DotDot]) continue;
gatherThread(i.name().toInt());
}
# endif
# else
if (GetProcessMemoryInfo(PRIVATE->hProc, &PRIVATE->mem_cnt, sizeof(PRIVATE->mem_cnt)) != 0) {
tstat.physical_memsize = PRIVATE->mem_cnt.WorkingSetSize;
}
tstat.priority = GetPriorityClass(PRIVATE->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 == DWORD(pID_)) {
++thcnt;
gatherThread(thread.th32ThreadID);
}
while (Thread32Next(snap, &thread) == TRUE) {
if (thread.th32OwnerProcessID == DWORD(pID_)) {
++thcnt;
gatherThread(thread.th32ThreadID);
}
// piCout << thread.th32ThreadID;
}
}
tstat.threads = thcnt;
CloseHandle(snap);
}
FILETIME ft0, ft1, ft_kernel, ft_user;
double el_s = PRIVATE->tm.elapsed_s() * cpu_count / 100.;
if (GetProcessTimes(PRIVATE->hProc, &ft0, &ft1, &ft_kernel, &ft_user) != 0) {
PISystemTime tm_kernel_c = FILETIME2PISystemTime(ft_kernel);
PISystemTime tm_user_c = FILETIME2PISystemTime(ft_user);
if (cycle < 0) {
PRIVATE->tm_kernel = tm_kernel_c;
PRIVATE->tm_user = tm_user_c;
}
cycle = 0;
if (el_s <= 0.) {
tstat.cpu_load_system = 0.f;
tstat.cpu_load_user = 0.f;
} else {
tstat.cpu_load_system = (tm_kernel_c - PRIVATE->tm_kernel).toSeconds() / el_s;
tstat.cpu_load_user = (tm_user_c - PRIVATE->tm_user).toSeconds() / el_s;
}
PRIVATE->tm_kernel = tm_kernel_c;
PRIVATE->tm_user = tm_user_c;
} else {
tstat.cpu_load_system = 0.f;
tstat.cpu_load_user = 0.f;
}
PRIVATE->tm.reset();
# endif
#endif
tstat.cpu_load_system = piClampf(tstat.cpu_load_system, 0.f, 100.f);
tstat.cpu_load_user = piClampf(tstat.cpu_load_user, 0.f, 100.f);
auto i = cur_tm.makeIterator();
while (i.next()) {
if (!last_tm.contains(i.key())) continue;
ThreadStats & ts_new(i.value());
ThreadStats & ts_old(last_tm[i.key()]);
ts_new.cpu_load_kernel = calcThreadUsage(ts_new.kernel_time, ts_old.kernel_time);
ts_new.cpu_load_user = calcThreadUsage(ts_new.user_time, ts_old.user_time);
// piCout << ts_new.cpu_load_user;
}
last_tm = cur_tm;
lock();
cur_ts = cur_tm.values();
unlock();
tstat.ram_total = totalRAM();
tstat.ram_used = usedRAM();
tstat.ram_free = freeRAM();
stat_mutex.lock();
stat = tstat;
stat.makeStrings();
stat_mutex.unlock();
measured();
}
void PISystemMonitor::gatherThread(llong id) {
PISystemMonitor::ThreadStats ts;
if (id == 0) return;
ts.id = id;
#ifdef MICRO_PIP
ts.name = tbid.value(id, "<PIThread>");
#else
ts.name = tbid.value(id, "<non-PIThread>");
# ifndef WINDOWS
PIFile f(PRIVATE->proc_dir + "task/" + PIString::fromNumber(id) + "/stat");
// piCout << f.path();
if (!f.open(PIIODevice::ReadOnly)) return;
PIString str = PIString::fromAscii(f.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;
}
}
str.cutMid(si - 1, fi - si + 3);
PIStringList sl = str.split(" ");
if (sl.size_s() < 14) return;
// piCout << sl[0] << sl[12] << sl[13];
ts.user_time = PISystemTime::fromMilliseconds(sl[12].toInt() * 10.);
ts.kernel_time = PISystemTime::fromMilliseconds(sl[13].toInt() * 10.);
# else
PISystemTime ct = PISystemTime::current();
FILETIME times[4];
HANDLE thdl = OpenThread(THREAD_QUERY_INFORMATION, FALSE, DWORD(id));
if (!thdl) {
piCoutObj << "GatherThread(" << id << "):: OpenThread() error:" << errorString();
return;
}
if (GetThreadTimes(thdl, &(times[0]), &(times[1]), &(times[2]), &(times[3])) == 0) {
CloseHandle(thdl);
piCoutObj << "GatherThread(" << id << "):: GetThreadTimes() error:" << errorString();
return;
}
CloseHandle(thdl);
ts.created = FILETIME2PIDateTime(times[0]);
ts.work_time = ct - ts.created.toSystemTime();
ts.kernel_time = FILETIME2PISystemTime(times[2]);
ts.user_time = FILETIME2PISystemTime(times[3]);
# endif
#endif
cur_tm[id] = ts;
}
float PISystemMonitor::calcThreadUsage(PISystemTime & t_new, PISystemTime & t_old) {
if (delay_.isNull()) return -1.;
return piClampf(100. * ((t_new - t_old).toMilliseconds() / delay_.toMilliseconds()), 0.f, 100.f);
}
ullong PISystemMonitor::totalRAM() {
#ifdef ESP_PLATFORM
multi_heap_info_t heap_info;
piZeroMemory(heap_info);
heap_caps_get_info(&heap_info, MALLOC_CAP_8BIT);
return heap_info.total_allocated_bytes + heap_info.total_free_bytes;
#endif
return 0;
}
ullong PISystemMonitor::freeRAM() {
#ifdef ESP_PLATFORM
multi_heap_info_t heap_info;
piZeroMemory(heap_info);
heap_caps_get_info(&heap_info, MALLOC_CAP_8BIT);
return heap_info.total_free_bytes;
#endif
return 0;
}
ullong PISystemMonitor::usedRAM() {
#ifdef ESP_PLATFORM
multi_heap_info_t heap_info;
piZeroMemory(heap_info);
heap_caps_get_info(&heap_info, MALLOC_CAP_8BIT);
return heap_info.total_allocated_bytes;
#endif
return 0;
}
PISystemMonitor::Pool * PISystemMonitor::Pool::instance() {
static Pool ret;
return &ret;
}
PISystemMonitor * PISystemMonitor::Pool::getByPID(int pID) {
PIMutexLocker _ml(mutex);
return sysmons.value(pID, 0);
}
void PISystemMonitor::Pool::add(PISystemMonitor * sm) {
PIMutexLocker _ml(mutex);
sysmons[sm->pID()] = sm;
}
void PISystemMonitor::Pool::remove(PISystemMonitor * sm) {
PIMutexLocker _ml(mutex);
sysmons.remove(sm->pID());
}