diff --git a/btop.cpp b/btop.cpp index 9c0f478..2fd9e3d 100644 --- a/btop.cpp +++ b/btop.cpp @@ -25,6 +25,7 @@ tab-size = 4 #include #include #include +#include #include @@ -35,26 +36,20 @@ tab-size = 4 #include #if defined(__linux__) - #define SYSTEM "linux" #include #elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) #include #if defined(BSD) - #define SYSTEM "bsd" - #else - #define SYSTEM "unknown" + // #include #endif #elif defined(__APPLE__) && defined(__MACH__) #include #if TARGET_OS_MAC == 1 - #define SYSTEM "osx" - #else - #define SYSTEM "unknown" + // #include #endif -#else - #define SYSTEM "unknown" #endif +namespace fs = std::filesystem; using namespace std; @@ -99,7 +94,7 @@ class C_Banner { string banner_str; public: - int width; + int width = 0; C_Banner(){ size_t z = 0; @@ -112,7 +107,7 @@ public: fg = hex_to_color(line[0]); bg_i = 120-z*12; bg = dec_to_color(bg_i, bg_i, bg_i); - for (unsigned i = 0; i < line[1].size(); i += 3) { + for (uint i = 0; i < line[1].size(); i += 3) { if (line[1][i] == ' '){ letter = ' '; i -= 2; @@ -125,7 +120,7 @@ public: oc = b_color; } z++; - if (z < Global::Banner_src.size()) out += Mv::l(ulen(line[1])) + Mv::d(1); + if (z < Global::Banner_src.size()) out += Mv::l(new_len) + Mv::d(1); } banner_str = out + Mv::r(18 - Global::Version.size()) + Fx::i + dec_to_color(0,0,0, State::truecolor, "bg") + dec_to_color(150, 150, 150) + "v" + Global::Version; } @@ -154,6 +149,15 @@ int main(int argc, char **argv){ cout.setf(std::ios::boolalpha); if (argc > 1) argumentParser(argc, argv); + //? Init for Linux + if (Global::SYSTEM == "linux") { + Global::proc_path = (fs::is_directory(fs::path("/proc"))) ? fs::path("/proc") : Global::proc_path; + if (Global::proc_path.empty()) { + cout << "ERROR: Proc filesystem not detected!" << endl; + exit(1); + } + } + //? Initialize terminal and set options C_Term Term; if (!Term.initialized) { @@ -174,7 +178,6 @@ int main(int argc, char **argv){ //? Initialize the Input class C_Input Input; - //* ------------------------------------------------ TESTING ------------------------------------------------------ int debug = 2; @@ -192,6 +195,7 @@ int main(int argc, char **argv){ + //* Test MENUS // for (auto& outer : Global::Menus){ // for (auto& inner : outer.second){ @@ -264,10 +268,10 @@ int main(int argc, char **argv){ // insert Processes call here - - unsigned lc; + uint 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 << rjustify("Pid:", 8) << " " << ljustify("Program:", 16) << " " << ljustify("Command:", Term.width - 69) << " Threads: " << + ljustify("User:", 10) << " " << rjustify("MemB", 5) << " " << rjustify("Cpu%", 14) << "\n" << Mv::save << flush; while (Input() != "q") { timestamp = time_ms(); @@ -275,9 +279,10 @@ int main(int argc, char **argv){ timestamp = time_ms() - timestamp; ostring.clear(); lc = 0; - for (auto& [lpid, lname, lcmd, luser, lcpu, lcpu_s] : plist){ + for (auto& [lpid, lname, lcmd, lthread, luser, lmem, 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 += rjustify(to_string(lpid), 8) + " " + ljustify(lname, 16) + " " + ljustify(lcmd, Term.width - 66, true) + " " + + rjustify(to_string(lthread), 5) + " " + ljustify(luser, 10) + " " + rjustify(floating_humanizer(lmem, true, 1), 5) + string(11, ' '); ostring += (lcpu > 100) ? rjustify(to_string(lcpu), 3) + " " : rjustify(to_string(lcpu), 4); ostring += "\n"; if (lc++ > Term.height - 20) break; diff --git a/src/btop_config.h b/src/btop_config.h index 5e7c3d2..b5971b9 100644 --- a/src/btop_config.h +++ b/src/btop_config.h @@ -39,7 +39,7 @@ using namespace std; namespace State { bool truecolor = true; string fg, bg; - unsigned width, height; + uint width, height; } class C_Config { diff --git a/src/btop_globs.h b/src/btop_globs.h index 6d6deae..f5011f1 100644 --- a/src/btop_globs.h +++ b/src/btop_globs.h @@ -113,6 +113,11 @@ namespace Global { } } } } }; + + //? Units for floating_humanizer function + const vector Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"}; + const vector Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"}; + } #endif diff --git a/src/btop_linux.h b/src/btop_linux.h index 91dd4ea..1b9bea3 100644 --- a/src/btop_linux.h +++ b/src/btop_linux.h @@ -37,23 +37,34 @@ tab-size = 4 namespace fs = std::filesystem; using namespace std; +namespace Global { + + const string SYSTEM = "linux"; + filesystem::path proc_path; + +} + class Processes { uint64_t tstamp; long int clk_tck; map> cache; - map sorts = { + map sorts = { {"pid", 0}, {"name", 1}, {"command", 2}, - {"user", 3}, - {"cpu direct", 4}, - {"cpu lazy", 5} + {"threads", 3}, + {"user", 4}, + {"memory", 5}, + {"cpu direct", 6}, + {"cpu lazy", 7} }; map uid_user; + fs::path passwd_path; fs::file_time_type passwd_time; map cpu_times; map cpu_second; - unsigned counter = 0; + uint counter = 0; + long page_size = sysconf(_SC_PAGE_SIZE); public: atomic stop; atomic running; @@ -62,40 +73,42 @@ public: auto collect(string sorting="pid", bool reverse=false, string filter=""){ running.store(true); int pid; - uint64_t cpu_t; + uint64_t cpu_t, rss_mem; double cpu, cpu_s; + size_t threads; ifstream pread; - string pid_str, name, cmd, attr, user, instr, uid, status, tmpstr; + string pid_str, name, cmd, attr, user, instr, uid, status, tmpstr, smap; 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; + //? Return type! Values in tuple: pid, program, command, threads, username, mem KiB, 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) { + if (!passwd_path.empty() && fs::last_write_time(passwd_path) != passwd_time) { string r_uid, r_user; - passwd_time = fs::last_write_time(fs::path("/etc/passwd")); + passwd_time = fs::last_write_time(passwd_path); 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; + ifstream pread(passwd_path); + if (pread.good()) { + 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(); } //* Iterate over all pid directories in /proc and get relevant values - for (auto& d: fs::directory_iterator("/proc")){ + for (auto& d: fs::directory_iterator(Global::proc_path)){ if (stop.load()) { procs.clear(); running.store(false); @@ -104,16 +117,22 @@ public: } pid_str = fs::path(d.path()).filename(); cpu = 0.0; + rss_mem = 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(); + //* Get cpu usage, threads and rss mem from [pid]/stat + if (fs::exists((string)d.path() + "/stat")) { + pread.clear(); pstat.clear(); ifstream pread((string)d.path() + "/stat"); - while (getline(pread, instr, ' ')) pstat.push_back(instr); + if (pread.good()) while (getline(pread, instr, ' ')) pstat.push_back(instr); pread.close(); + if (pstat.size() < 37) continue; + + //? Process number of threads + threads = stoul(pstat[19]); + //? Process utime + stime cpu_t = stoull(pstat[13]) + stoull(pstat[14]); if (!cpu_times.contains(pid)) cpu_times[pid] = cpu_t; @@ -121,6 +140,9 @@ public: //? Cache process start time if (!cpu_second.contains(pid)) cpu_second[pid] = stoull(pstat[21]); + //? Get RSS memory in KiB (will be overriden by /status if available) + rss_mem = (stoull(pstat[23]) * page_size) >> 10; + //? 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; @@ -129,86 +151,106 @@ public: cpu_times[pid] = cpu_t; } + //* Get RSS memory in KiB + if (fs::exists((string)d.path() + "/status")) { + pread.clear(); status.clear(); tmpstr.clear(); + ifstream pread((string)d.path() + "/status"); + if (pread.good()) { + while (getline(pread, status, ':')){ + if (status == "VmRSS") { + pread.ignore(); + pread >> ws; + getline(pread, tmpstr, 'k'); + tmpstr.pop_back(); + break; + } else pread.ignore(numeric_limits::max(), '\n'); + } + } + pread.close(); + if (!tmpstr.empty()) rss_mem = stoull(tmpstr); + } + //* Cache program name, command and username if (!cache.contains(pid)) { - if (fs::is_regular_file((string)d.path() + "/comm")) { + if (fs::exists((string)d.path() + "/comm")) { + pread.clear(); name.clear(); ifstream pread((string)d.path() + "/comm"); - getline(pread, name); + if (pread.good()) getline(pread, name); pread.close(); } - if (fs::is_regular_file((string)d.path() + "/cmdline")) { - cmd.clear(); + if (fs::exists((string)d.path() + "/cmdline")) { + pread.clear(); cmd.clear(); tmpstr.clear(); ifstream pread((string)d.path() + "/cmdline"); - while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " "; + if (pread.good()) while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " "; pread.close(); if (!cmd.empty()) cmd.pop_back(); } if (fs::exists((string)d.path() + "/status")) { + pread.clear(); status.clear(); uid.clear(); 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'); + if (pread.good()) { + 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) : ""; + user = (!uid.empty() && uid_user.contains(uid)) ? uid_user.at(uid) : uid; } cache[pid] = make_tuple(name, cmd, user); } // //* 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 + pid_str.find(filter) == string::npos && //? Pid + get<0>(cache[pid]).find(filter) == string::npos && //? Program + get<1>(cache[pid]).find(filter) == string::npos && //? Command + get<2>(cache[pid]).find(filter) == string::npos //? User ) continue; //* Create tuple - procs.push_back(make_tuple(pid, get<0>(cache[pid]), get<1>(cache[pid]), get<2>(cache[pid]), cpu, cpu_s)); + procs.push_back(make_tuple(pid, get<0>(cache[pid]), get<1>(cache[pid]), threads, get<2>(cache[pid]), rss_mem, cpu, cpu_s)); } } // auto st = time_ms(); //* Sort processes vector - ranges::sort(procs, [&sortint, &reverse](tuple& a, tuple& b) { + ranges::sort(procs, [&sortint, &reverse]( tuple& a, + tuple& b) + { switch (sortint) { 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 + case 3: return (reverse) ? get<3>(a) < get<3>(b) : get<3>(a) > get<3>(b); //? Threads + case 4: return (reverse) ? get<4>(a) < get<4>(b) : get<4>(a) > get<4>(b); //? User + case 5: return (reverse) ? get<5>(a) < get<5>(b) : get<5>(a) > get<5>(b); //? Memory + case 6: return (reverse) ? get<6>(a) < get<6>(b) : get<6>(a) > get<6>(b); //? Cpu direct + case 7: return (reverse) ? get<7>(a) < get<7>(b) : get<7>(a) > get<7>(b); //? Cpu lazy } return false; } ); - //* When using "cpu lazy" sorting, push processes with high cpu usage to the front regardless of cumulative usage - if (sortint == 5 && !reverse) { + //* When using "cpu lazy" sorting push processes with high cpu usage to the front regardless of cumulative usage + if (sortint == 6 && !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]); + if (i <= 5 && get<6>(procs[i]) > max) max = get<6>(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); + if (i == offset && get<6>(procs[i]) > 30.0) offset++; + else if (get<6>(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 + //* Clear all cached values at a regular interval to get rid of dead processes if (++counter >= 10000 || (filter.empty() && cache.size() > procs.size() + 100)) { counter = 0; cache.clear(); @@ -225,6 +267,7 @@ public: clk_tck = sysconf(_SC_CLK_TCK); tstamp = time_ms(); stop.store(false); + passwd_path = (fs::exists(fs::path("/etc/passwd"))) ? fs::path("/etc/passwd") : passwd_path; collect(); } }; diff --git a/src/btop_theme.h b/src/btop_theme.h index 9ec5ee1..b8b70ca 100644 --- a/src/btop_theme.h +++ b/src/btop_theme.h @@ -33,7 +33,7 @@ using namespace std; //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //* Convert 24-bit colors to 256 colors using 6x6x6 color cube -int truecolor_to_256(unsigned r, unsigned g, unsigned b){ +int truecolor_to_256(uint r, uint g, uint b){ if (r / 11 == g / 11 && g / 11 == b / 11) { return 232 + r / 11; } else { @@ -54,7 +54,7 @@ string hex_to_color(string hexa, bool t_to_256=false, string depth="fg"){ pre += (t_to_256) ? "5;" : "2;"; if (hexa.size() == 2){ - unsigned h_int = stoi(hexa, 0, 16); + uint h_int = stoi(hexa, 0, 16); if (t_to_256){ return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m"; } else { @@ -83,7 +83,7 @@ string hex_to_color(string hexa, bool t_to_256=false, string depth="fg"){ //* Args r: [0-255], g: [0-255], b: [0-255] //* t_to_256: [true|false] convert 24bit value to 256 color value //* depth: ["fg"|"bg"] for either a foreground color or a background color -string dec_to_color(unsigned r, unsigned g, unsigned b, bool t_to_256=false, string depth="fg"){ +string dec_to_color(uint r, uint g, uint b, bool t_to_256=false, string depth="fg"){ depth = (depth == "fg") ? "38" : "48"; string pre = Fx::e + depth + ";"; pre += (t_to_256) ? "5;" : "2;"; diff --git a/src/btop_tools.h b/src/btop_tools.h index 624f8ac..6623e61 100644 --- a/src/btop_tools.h +++ b/src/btop_tools.h @@ -42,7 +42,7 @@ using namespace std; //* Collection of escape codes for text style and formatting namespace Fx { //* Escape sequence start - const string e = "\x1b["; + const string e = "\033["; //* Bold on const string b = e + "1m"; @@ -96,9 +96,9 @@ namespace Mv { //? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //* Return number of UTF8 characters in a string -size_t ulen(string s){ +inline size_t ulen(string s){ return std::count_if(s.begin(), s.end(), - [](char c) { return (static_cast(c) & 0xC0) != 0x80; } ); + [](char& c) { return (c & 0xC0) != 0x80; } ); } //* Return current time since epoch in milliseconds @@ -152,7 +152,7 @@ vector ssplit(string str, string delim = " ", int times = 0){ } //* Put current thread to sleep for milliseconds -void sleep_ms(unsigned ms) { +void sleep_ms(uint ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } @@ -207,9 +207,9 @@ string trans(string str){ return (newstr.empty()) ? str : newstr; } -string sec_to_dhms(unsigned sec){ +string sec_to_dhms(uint sec){ string out; - unsigned d, h, m; + uint d, h, m; d = sec / (3600 * 24); sec %= 3600 * 24; h = sec / 3600; @@ -231,6 +231,41 @@ double system_uptime(){ return stod(upstr); } +//* Scales up in steps of 1024 to highest possible unit and returns string with unit suffixed +//* bit=True or defaults to bytes +//* start=int to set 1024 multiplier starting unit +//* short=True always returns 0 decimals and shortens unit to 1 character +string floating_humanizer(uint64_t value, bool shorten=false, uint start=0, bool bit=false, bool per_second=false){ + string out; + uint mult = (bit) ? 8 : 1; + auto& units = (bit) ? Global::Units_bit : Global::Units_byte; + + value *= 100 * mult; + + while (value >= 102400){ + value >>= 10; + if (value < 100){ + out = to_string(value); + break; + } + start++; + } + if (out.empty()) { + out = to_string(value); + if (out.size() == 4 && start > 0) { out.pop_back(); out.insert(2, ".");} + else if (out.size() == 3 && start > 0) out.insert(1, "."); + else if (out.size() >= 2) out.resize(out.size() - 2); + } + if (shorten){ + if (out.find('.') != string::npos) out = to_string((int)round(stof(out))); + if (out.size() > 3) { out = to_string((int)(out[0] - '0') + 1); start++;} + out.push_back(units[start][0]); + } + else out += " " + units[start]; + + if (per_second) out += (bit) ? "ps" : "/s"; + return out; +} //? --------------------------------------------------- CLASSES ----------------------------------------------------- @@ -240,8 +275,8 @@ class C_Term { public: bool initialized = false; bool resized = false; - unsigned width = 0; - unsigned height = 0; + uint width = 0; + uint height = 0; //* Hide terminal cursor const string hide_cursor = Fx::e + "?25l";