Files
pip/libs/main/application/pisystemmonitor.cpp
peri4 caa7880cc4 get rid of piForeach
apply some code analyzer recommendations
ICU flag now check if libicu exists
prepare for more accurate growth of containers (limited PoT, then constantly increase size)
2024-11-20 20:01:47 +03:00

460 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();
}
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;
memset(&heap_info, 0, sizeof(multi_heap_info_t));
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;
memset(&heap_info, 0, sizeof(multi_heap_info_t));
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;
memset(&heap_info, 0, sizeof(multi_heap_info_t));
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());
}