/* 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 . */ #include "piincludes_p.h" #include "pisystemmonitor.h" #include "pisysteminfo.h" #include "piprocess.h" #include "pidir.h" #include "pitime_win.h" #ifdef WINDOWS # include # include #endif #ifdef MAC_OS struct kqueue_id_t; # include # include #endif #ifdef ESP_PLATFORM # include "esp_heap_caps.h" #endif PISystemMonitor::ProcessStatsFixed::ProcessStatsFixed() { 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; } 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); } PISystemMonitor::ThreadStatsFixed::ThreadStatsFixed() { id = 0; cpu_load_kernel = cpu_load_user = -1.f; } #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"); } PISystemMonitor::~PISystemMonitor() { stop(); } #ifndef MICRO_PIP bool PISystemMonitor::startOnProcess(int pID, int interval_ms) { 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 = " << 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 = " << pID_ << "," << errorString(); return false; } PRIVATE->tm.reset(); #endif return start(interval_ms); } #endif bool PISystemMonitor::startOnSelf(int interval_ms) { #ifndef MICRO_PIP bool ret = startOnProcess(PIProcess::currentPID(), interval_ms); cycle = -1; #else bool ret = start(interval_ms); #endif return ret; } PIVector PISystemMonitor::threadsStatistic() const { lock(); PIVector 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::stop(); #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 tv = pitc->threads(); piForeach (PIThread * t, tv) if (t->isPIObject()) tbid[t->tid()] = t->name(); pitc->unlock(); //piCout << tbid.keys().toType(); ProcessStats tstat; tstat.ID = pID_; #ifdef MICRO_PIP piForeach (PIThread * t, tv) if (t->isPIObject()) gatherThread(t->tid()); #else #ifndef WINDOWS 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_; tstat.cpu_load_user = 100.f * (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev).toMilliseconds() / delay_; cycle = 0; //piCout << (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev).toMilliseconds() / delay_; # else PRIVATE->file.seekToBegin(); PIString str(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_ / 1000.); tstat.cpu_load_user = (PRIVATE->cpu_u_cur - PRIVATE->cpu_u_prev) / (delay_ / 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 = 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 tld = PIDir(PRIVATE->proc_dir + "task").entries(); piForeachC (PIFile::FileInfo & 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.valueRef()); 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, ""); #else ts.name = tbid.value(id, ""); # ifndef WINDOWS PIFile f(PRIVATE->proc_dir + "task/" + PIString::fromNumber(id) + "/stat"); //piCout << f.path(); if (!f.open(PIIODevice::ReadOnly)) return; PIString str = 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) { piCout << "[PISystemMonitor] gatherThread(" << id << "):: OpenThread() error:" << errorString(); return; } if (GetThreadTimes(thdl, &(times[0]), &(times[1]), &(times[2]), &(times[3])) == 0) { CloseHandle(thdl); piCout << "[PISystemMonitor] 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_ <= 0) return -1.; return piClampf(100. * ((t_new - t_old).toMilliseconds() / delay_), 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()); } PIByteArray & operator <<(PIByteArray & s, const PISystemMonitor::ProcessStats & v) { s << PIByteArray::RawData(&v, sizeof(PISystemMonitor::ProcessStatsFixed)) << v.exec_name << v.state; return s; } PIByteArray & operator >>(PIByteArray & s, PISystemMonitor::ProcessStats & v) { s >> PIByteArray::RawData(&v, sizeof(PISystemMonitor::ProcessStatsFixed)) >> v.exec_name >> v.state; v.makeStrings(); return s; } PIByteArray & operator <<(PIByteArray & s, const PISystemMonitor::ThreadStats & v) { s << PIByteArray::RawData(&v, sizeof(PISystemMonitor::ThreadStatsFixed)) << v.name; return s; } PIByteArray & operator >>(PIByteArray & s, PISystemMonitor::ThreadStats & v) { s >> PIByteArray::RawData(&v, sizeof(PISystemMonitor::ThreadStatsFixed)) >> v.name; return s; }