diff --git a/Makefile b/Makefile index 5159a1e..cbda712 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ PREFIX ?= /usr/local DOCDIR ?= $(PREFIX)/share/btop/doc CXX = g++ -CXXFLAGS = -std=c++20 -pthread -Wall -Wextra +CXXFLAGS = -std=c++20 -pthread -Wall INCLUDES = -I./src btop: btop.cpp diff --git a/btop.cpp b/btop.cpp index 88412e6..9c0f478 100644 --- a/btop.cpp +++ b/btop.cpp @@ -36,7 +36,6 @@ tab-size = 4 #if defined(__linux__) #define SYSTEM "linux" - #include #include #elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) #include @@ -245,40 +244,49 @@ int main(int argc, char **argv){ } } - struct sysinfo sinfo; - sysinfo(&sinfo); - cout << "Up for " << sec_to_dhms(sinfo.uptime) << endl; + cout << "Up for " << sec_to_dhms(round(system_uptime())) << endl; //------>>>>>> + auto timestamp = time_ms(); + Processes Proc; + + cout << "Total Processes init: " << time_ms() - timestamp << "ms" << endl; + + sleep_ms(1000); + // insert Processes call here - timestamp = time_ms() - timestamp; - - auto timestamp2 = time_ms(); - - // insert Processes call here - - timestamp2 = time_ms() - timestamp2; - // int lc = 0; - // string ostring; - // cout << rjustify("Pid:", 8) << " " << ljustify("Program:", 16) << " " << ljustify("Command:", Term.width - 50) << " " << rjustify("User:", 10) << " " << rjustify("Group:", 10) << "\n"; - // for (auto& [lpid, lname, lcmd, luser, lgroup] : plist){ - // ostring += rjustify(to_string(lpid), 8) + " " + ljustify(lname, 16) + " " + ljustify(lcmd, Term.width - 50, true) + " " + rjustify(luser, 10) + " " + rjustify(lgroup, 10) + "\n"; - // if (lc++ > Term.height - 30) break; - // } + unsigned lc; + string ostring; + cout << rjustify("Pid:", 8) << " " << ljustify("Program:", 16) << " " << ljustify("Command:", Term.width - 48) << " " << rjustify("User:", 10) << " " << rjustify("Cpu%", 4) << "\n" << Mv::save << flush; - // cout << ostring << endl; + while (Input() != "q") { + timestamp = time_ms(); + auto plist = Proc.collect("cpu lazy", false); + timestamp = time_ms() - timestamp; + ostring.clear(); + lc = 0; + for (auto& [lpid, lname, lcmd, luser, lcpu, lcpu_s] : plist){ + (void) lcpu_s; + ostring += rjustify(to_string(lpid), 8) + " " + ljustify(lname, 16) + " " + ljustify(lcmd, Term.width - 48, true) + " " + rjustify(luser, 10) + " "; + ostring += (lcpu > 100) ? rjustify(to_string(lcpu), 3) + " " : rjustify(to_string(lcpu), 4); + ostring += "\n"; + if (lc++ > Term.height - 20) break; + } + cout << Mv::restore << ostring << endl; + cout << "Processes call took: " << timestamp << "ms." << endl; + Input(2000); + } - // cout << "List generated in " << timestamp << "ms first call and in " << timestamp2 << "ms second call" << endl; // cout << "Found " << plist.size() << " pids\n" << endl; //-----<<<<< diff --git a/src/btop_globs.h b/src/btop_globs.h index 4c45d73..6d6deae 100644 --- a/src/btop_globs.h +++ b/src/btop_globs.h @@ -115,4 +115,4 @@ namespace Global { }; } -#endif \ No newline at end of file +#endif diff --git a/src/btop_linux.h b/src/btop_linux.h index d158d6c..91dd4ea 100644 --- a/src/btop_linux.h +++ b/src/btop_linux.h @@ -29,9 +29,6 @@ tab-size = 4 #include #include -#include -// #include -#include #include #include @@ -41,82 +38,197 @@ namespace fs = std::filesystem; using namespace std; class Processes { - uint64_t timestamp; + uint64_t tstamp; + long int clk_tck; map> cache; map sorts = { {"pid", 0}, {"name", 1}, {"command", 2}, - {"user", 3} + {"user", 3}, + {"cpu direct", 4}, + {"cpu lazy", 5} }; + map uid_user; + fs::file_time_type passwd_time; + map cpu_times; + map cpu_second; + unsigned counter = 0; +public: + atomic stop; + atomic running; + + //* Collects process information from /proc and returns a vector of tuples + auto collect(string sorting="pid", bool reverse=false, string filter=""){ + running.store(true); + int pid; + uint64_t cpu_t; + double cpu, cpu_s; + ifstream pread; + string pid_str, name, cmd, attr, user, instr, uid, status, tmpstr; + auto since_last = time_ms() - tstamp; + if (since_last < 1) since_last = 1; + auto uptime = system_uptime(); + auto sortint = (sorts.contains(sorting)) ? sorts[sorting] : 5; + vector pstat; + + //? Return type! Values in tuple: pid, program, command, username, cpu%, cpu cumulative + vector> procs; + + //* Update uid_user map if /etc/passwd changed since last run + if (fs::last_write_time(fs::path("/etc/passwd")) != passwd_time) { + string r_uid, r_user; + passwd_time = fs::last_write_time(fs::path("/etc/passwd")); + uid_user.clear(); + pread.clear(); + ifstream pread("/etc/passwd"); + while (true){ + getline(pread, r_user, ':'); + pread.ignore(numeric_limits::max(), ':'); + getline(pread, r_uid, ':'); + uid_user[r_uid] = r_user; + pread.ignore(numeric_limits::max(), '\n'); + if (pread.eof()) break; + } + pread.close(); + } - auto collect(string sorting="pid", bool reverse=false){ - int pid, count = 0; - auto timestamp = time_ms(); - string item, name, cmd, attr, user; - struct stat ug; - auto sortint = (sorts.contains(sorting)) ? sorts[sorting] : 0; - vector> procs; + //* Iterate over all pid directories in /proc and get relevant values for (auto& d: fs::directory_iterator("/proc")){ - item = fs::path(d.path()).filename(); - bool cached = false; - if (d.is_directory() && isdigit(item[0])) { - pid = stoi(item); - if (cache.contains(pid)) { - cached = true; - } else { + if (stop.load()) { + procs.clear(); + running.store(false); + stop.store(false); + return procs; + } + pid_str = fs::path(d.path()).filename(); + cpu = 0.0; + if (d.is_directory() && isdigit(pid_str[0])) { + pid = stoi(pid_str); + + //* Get cpu usage + if (fs::is_regular_file((string)d.path() + "/stat")) { + pstat.clear(); + ifstream pread((string)d.path() + "/stat"); + while (getline(pread, instr, ' ')) pstat.push_back(instr); + pread.close(); + + //? Process utime + stime + cpu_t = stoull(pstat[13]) + stoull(pstat[14]); + if (!cpu_times.contains(pid)) cpu_times[pid] = cpu_t; + + //? Cache process start time + if (!cpu_second.contains(pid)) cpu_second[pid] = stoull(pstat[21]); + + //? Process cpu usage since last update, 100'000 because (100 percent * 1000 milliseconds) for correct conversion + cpu = static_cast(100000 * (cpu_t - cpu_times[pid]) / since_last) / clk_tck; + + //? Process cumulative cpu usage since process start + cpu_s = static_cast((cpu_t / clk_tck) / (uptime - (cpu_second[pid] / clk_tck))); + cpu_times[pid] = cpu_t; + } + + //* Cache program name, command and username + if (!cache.contains(pid)) { if (fs::is_regular_file((string)d.path() + "/comm")) { ifstream pread((string)d.path() + "/comm"); getline(pread, name); pread.close(); } if (fs::is_regular_file((string)d.path() + "/cmdline")) { + cmd.clear(); ifstream pread((string)d.path() + "/cmdline"); - getline(pread, cmd); - // if (cmd.size() > 1) cmd.erase(cmd.size(), 1); - // cmd = to_string(ulen(cmd)) + ":" + to_string(cmd.size()) + cmd; + while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " "; pread.close(); + if (!cmd.empty()) cmd.pop_back(); } - if (fs::is_regular_file((string)d.path() + "/attr")) { - attr = (string)d.path() + "/attr"; - stat(attr.c_str(), &ug); // Error check omitted - struct passwd *pw = getpwuid(ug.st_uid); - user = pw->pw_name; - // struct group *gr = getgrgid(ug.st_gid); + if (fs::exists((string)d.path() + "/status")) { + ifstream pread((string)d.path() + "/status"); + status.clear(); + while (!pread.eof()){ + getline(pread, status, ':'); + if (status == "Uid") { + pread.ignore(); + getline(pread, uid, '\t'); + break; + } else { + pread.ignore(numeric_limits::max(), '\n'); + } + } + pread.close(); + user = (uid_user.contains(uid)) ? uid_user.at(uid) : ""; } - cache[pid] = make_tuple(name, clean_nullbyte(cmd), user); + cache[pid] = make_tuple(name, cmd, user); } - procs.push_back(make_tuple(pid, get<0>(cache[pid]), get<1>(cache[pid]), get<2>(cache[pid]))); + // //* Match filter if applicable + if (!filter.empty() && + pid_str.find(filter) == string::npos && + get<0>(cache[pid]).find(filter) == string::npos && + get<1>(cache[pid]).find(filter) == string::npos && + get<2>(cache[pid]).find(filter) == string::npos + ) continue; + + //* Create tuple + procs.push_back(make_tuple(pid, get<0>(cache[pid]), get<1>(cache[pid]), get<2>(cache[pid]), cpu, cpu_s)); } } - ranges::sort(procs, [sortint, reverse](tuple& a, tuple& b) { - if (reverse) { + // auto st = time_ms(); + + //* Sort processes vector + ranges::sort(procs, [&sortint, &reverse](tuple& a, tuple& b) { switch (sortint) { - case 0: return get<0>(a) > get<0>(b); - case 1: return get<1>(a) > get<1>(b); - case 2: return get<2>(a) > get<2>(b); - case 3: return get<3>(a) > get<3>(b); - } - } else { - switch (sortint) { - case 0: return get<0>(a) < get<0>(b); - case 1: return get<1>(a) < get<1>(b); - case 2: return get<2>(a) < get<2>(b); - case 3: return get<3>(a) < get<3>(b); + case 0: return (reverse) ? get<0>(a) < get<0>(b) : get<0>(a) > get<0>(b); //? Pid + case 1: return (reverse) ? get<1>(a) < get<1>(b) : get<1>(a) > get<1>(b); //? Program + case 2: return (reverse) ? get<2>(a) < get<2>(b) : get<2>(a) > get<2>(b); //? Command + case 3: return (reverse) ? get<3>(a) < get<3>(b) : get<3>(a) > get<3>(b); //? User + case 4: return (reverse) ? get<4>(a) < get<4>(b) : get<4>(a) > get<4>(b); //? Cpu direct + case 5: return (reverse) ? get<5>(a) < get<5>(b) : get<5>(a) > get<5>(b); //? Cpu lazy } + return false; } - }); - // if (reverse) views::reverse(procs); + ); - timestamp = time_ms() - timestamp; + //* When using "cpu lazy" sorting, push processes with high cpu usage to the front regardless of cumulative usage + if (sortint == 5 && !reverse) { + double max = 10.0, target = 30.0; + for (size_t i = 0, offset = 0; i < procs.size(); i++) { + if (i <= 5 && get<4>(procs[i]) > max) max = get<4>(procs[i]); + else if (i == 6) target = (max > 30.0) ? max : 10.0; + if (i == offset && get<4>(procs[i]) > 30.0) offset++; + else if (get<4>(procs[i]) > target) rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1); + } + } + + + // cout << "Sort took: " << time_ms() - st << "ms " << flush; + + + + //* Clear all cached values at a regular interval + if (++counter >= 10000 || (filter.empty() && cache.size() > procs.size() + 100)) { + counter = 0; + cache.clear(); + cpu_times.clear(); + cpu_second.clear(); + } + + tstamp = time_ms(); + running.store(false); return procs; } + + Processes() { + clk_tck = sysconf(_SC_CLK_TCK); + tstamp = time_ms(); + stop.store(false); + collect(); + } }; -#endif \ No newline at end of file +#endif diff --git a/src/btop_tools.h b/src/btop_tools.h index 60e9103..624f8ac 100644 --- a/src/btop_tools.h +++ b/src/btop_tools.h @@ -26,6 +26,7 @@ tab-size = 4 #include #include #include +#include #include #include @@ -206,12 +207,6 @@ string trans(string str){ return (newstr.empty()) ? str : newstr; } -//* Clean string by replacing null byte '\0' with whitespace ' ' -string clean_nullbyte(string str){ - while (str.find('\0') != string::npos) str.replace(str.find('\0'), 1, " "); - return str; -} - string sec_to_dhms(unsigned sec){ string out; unsigned d, h, m; @@ -228,6 +223,14 @@ string sec_to_dhms(unsigned sec){ return out; } +double system_uptime(){ + string upstr; + ifstream pread("/proc/uptime"); + getline(pread, upstr, ' '); + pread.close(); + return stod(upstr); +} + //? --------------------------------------------------- CLASSES ----------------------------------------------------- diff --git a/src/tempCodeRunnerFile.h b/src/tempCodeRunnerFile.h new file mode 100644 index 0000000..f5501bc --- /dev/null +++ b/src/tempCodeRunnerFile.h @@ -0,0 +1 @@ +clean_nullbyte( \ No newline at end of file