diff --git a/btop.cpp b/btop.cpp index a4ea95a..19f2fe4 100644 --- a/btop.cpp +++ b/btop.cpp @@ -26,6 +26,8 @@ tab-size = 4 #include #include #include +#include +#include #include #include @@ -56,6 +58,7 @@ tab-size = 4 using std::string, std::vector, std::array, std::map, std::atomic, std::endl, std::cout, std::views::iota, std::list, std::accumulate; using std::flush, std::endl, std::future, std::string_literals::operator""s, std::future_status; +namespace fs = std::filesystem; using namespace Tools; @@ -151,9 +154,9 @@ int main(int argc, char **argv){ #if defined(LINUX) //? Linux init - Global::proc_path = (fs::is_directory(fs::path("/proc"))) ? fs::path("/proc") : Global::proc_path; + Global::proc_path = (fs::is_directory(fs::path("/proc")) && access("/proc", R_OK) != -1) ? fs::path("/proc") : Global::proc_path; if (Global::proc_path.empty()) { - cout << "ERROR: Proc filesystem not detected!" << endl; + cout << "ERROR: Proc filesystem not found/readable!" << endl; exit(1); } #endif @@ -161,7 +164,7 @@ int main(int argc, char **argv){ //? Initialize terminal and set options if (!Term::init()) { cout << "ERROR: No tty detected!" << endl; - cout << "Sorry, btop++ needs an interactive shell to run." << endl; + cout << "btop++ needs an interactive shell to run." << endl; exit(1); } @@ -171,7 +174,7 @@ int main(int argc, char **argv){ auto thts = time_ms(); //? Generate the theme - Theme::set(Global::Default_theme); + Theme::set(Theme::Default_theme); //? Create the btop++ banner Global::banner = createBanner(); @@ -233,7 +236,6 @@ int main(int argc, char **argv){ } - if (thread_test){ map> runners; @@ -263,26 +265,17 @@ int main(int argc, char **argv){ -//------>>>>>> +//*------>>>>>> Proc testing auto timestamp = time_ms(); Proc::init(); - cout << "Total Processes init: " << time_ms() - timestamp << "ms" << endl; - cout << "Press any key to start!" << Mv::l(100) << flush; - - // sleep_ms(1000); - // Input::wait(); - // Input::clear(); - - - // insert Processes call here uint lc; string ostring; - uint64_t tsl, timestamp2; + uint64_t tsl, timestamp2, rcount = 0; list avgtimes; uint timer = 1000; bool filtering = false; @@ -326,8 +319,8 @@ int main(int argc, char **argv){ avgtimes.push_front(timestamp); if (avgtimes.size() > 100) avgtimes.pop_back(); cout << pbox << ostring << Fx::reset << "\n" << endl; - cout << Mv::to(Term::height - 4, 1) << "Processes call took: " << timestamp << "ms. Average: " << accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size() << - "ms of " << avgtimes.size() << " samples. Drawing took: " << time_ms() - timestamp2 << "ms. " << endl; + cout << Mv::to(Term::height - 4, 1) << "Processes call took: " << rjust(to_string(timestamp), 4) << "ms. Average: " << rjust(to_string(accumulate(avgtimes.begin(), avgtimes.end(), 0) / avgtimes.size()), 3) << + "ms of " << avgtimes.size() << " samples. Drawing took: " << time_ms() - timestamp2 << "ms. Number of processes: " << Proc::numpids << ". Run count: " << ++rcount << " " << endl; while (time_ms() < tsl) { if (Input::poll(tsl - time_ms())) key = Input::get(); @@ -353,7 +346,7 @@ int main(int argc, char **argv){ // cout << "Found " << plist.size() << " pids\n" << endl; -//-----<<<<< +//*-----<<<<< //cout << pw->pw_name << "/" << gr->gr_name << endl; diff --git a/src/btop_draw.h b/src/btop_draw.h index d17bdd6..93a7a60 100644 --- a/src/btop_draw.h +++ b/src/btop_draw.h @@ -23,7 +23,6 @@ tab-size = 4 #include #include -#include #include #include diff --git a/src/btop_globs.h b/src/btop_globs.h index 53bc2a7..e1d592a 100644 --- a/src/btop_globs.h +++ b/src/btop_globs.h @@ -31,50 +31,7 @@ namespace Global { atomic stop_all(false); - const unordered_map Default_theme = { - { "main_bg", "#00" }, - { "main_fg", "#cc" }, - { "title", "#ee" }, - { "hi_fg", "#969696" }, - { "selected_bg", "#7e2626" }, - { "selected_fg", "#ee" }, - { "inactive_fg", "#40" }, - { "graph_text", "#60" }, - { "meter_bg", "#40" }, - { "proc_misc", "#0de756" }, - { "cpu_box", "#3d7b46" }, - { "mem_box", "#8a882e" }, - { "net_box", "#423ba5" }, - { "proc_box", "#923535" }, - { "div_line", "#30" }, - { "temp_start", "#4897d4" }, - { "temp_mid", "#5474e8" }, - { "temp_end", "#ff40b6" }, - { "cpu_start", "#50f095" }, - { "cpu_mid", "#f2e266" }, - { "cpu_end", "#fa1e1e" }, - { "free_start", "#223014" }, - { "free_mid", "#b5e685" }, - { "free_end", "#dcff85" }, - { "cached_start", "#0b1a29" }, - { "cached_mid", "#74e6fc" }, - { "cached_end", "#26c5ff" }, - { "available_start", "#292107" }, - { "available_mid", "#ffd77a" }, - { "available_end", "#ffb814" }, - { "used_start", "#3b1f1c" }, - { "used_mid", "#d9626d" }, - { "used_end", "#ff4769" }, - { "download_start", "#231a63" }, - { "download_mid", "#4f43a3" }, - { "download_end", "#b0a9de" }, - { "upload_start", "#510554" }, - { "upload_mid", "#7d4180" }, - { "upload_end", "#dcafde" }, - { "process_start", "#80d0a3" }, - { "process_mid", "#dcd179" }, - { "process_end", "#d45454" } - }; + const unordered_map>> Menus = { { "options", { @@ -115,25 +72,10 @@ namespace Global { } } }; - //? Units for floating_humanizer function - const array Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"}; - const array Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"}; + } -namespace Symbols { - const string h_line = "─"; - const string v_line = "│"; - const string left_up = "┌"; - const string right_up = "┐"; - const string left_down = "└"; - const string right_down = "┘"; - const string title_left = "┤"; - const string title_right = "├"; - const string div_up = "┬"; - const string div_down = "┴"; - const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; -} #endif diff --git a/src/btop_input.h b/src/btop_input.h index cf7235e..a922ae4 100644 --- a/src/btop_input.h +++ b/src/btop_input.h @@ -23,12 +23,16 @@ tab-size = 4 #include #include -#include #include using std::string, std::unordered_map, std::cin; using namespace Tools; +/* The input functions relies on the following std::cin options being set: + cin.sync_with_stdio(false); + cin.tie(NULL); + These will automatically be set when running Term::init() from btop_tools.h +*/ //* Functions and variables for handling keyboard and mouse input namespace Input { diff --git a/src/btop_linux.h b/src/btop_linux.h index aa56934..a4a5f96 100644 --- a/src/btop_linux.h +++ b/src/btop_linux.h @@ -30,7 +30,6 @@ tab-size = 4 #include #include - #include #include @@ -39,12 +38,13 @@ tab-size = 4 using std::string, std::vector, std::array, std::ifstream, std::atomic, std::numeric_limits, std::streamsize, std::unordered_map, std::deque, std::list; +using std::cout, std::flush, std::endl; namespace fs = std::filesystem; using namespace Tools; +const auto SSmax = std::numeric_limits::max(); namespace Global { - const string System = "linux"; fs::path proc_path; } @@ -64,8 +64,6 @@ double system_uptime(){ namespace Proc { namespace { uint64_t tstamp; - size_t numpids = 500; - long int clk_tck; struct p_cache { string name, cmd, user; uint64_t cpu_t = 0, cpu_s = 0; @@ -75,10 +73,12 @@ namespace Proc { fs::path passwd_path; fs::file_time_type passwd_time; uint counter = 0; - long page_size = sysconf(_SC_PAGE_SIZE); + auto page_size = sysconf(_SC_PAGE_SIZE); + auto clk_tck = sysconf(_SC_CLK_TCK); } + size_t numpids = 500; atomic stop (false); atomic running (false); array sort_array = { @@ -116,15 +116,13 @@ namespace Proc { bool new_cache; char state; int cpu_n, p_nice; - size_t threads; + size_t threads, s_pos, c_pos, s_count; 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 = (sort_map.contains(sorting)) ? sort_map[sorting] : 7; - vector pstat; - pstat.reserve(40); vector procs; procs.reserve((numpids + 10)); vector c_pids; @@ -136,35 +134,35 @@ namespace Proc { string r_uid, r_user; passwd_time = fs::last_write_time(passwd_path); uid_user.clear(); - ifstream pread(passwd_path); + pread.open(passwd_path); if (pread.good()) { while (true){ getline(pread, r_user, ':'); - pread.ignore(numeric_limits::max(), ':'); + pread.ignore(SSmax, ':'); getline(pread, r_uid, ':'); uid_user[r_uid] = r_user; - pread.ignore(numeric_limits::max(), '\n'); + pread.ignore(SSmax, '\n'); if (pread.eof()) break; } } pread.close(); } - //* Iterate over all pids in /proc and get relevant values for (auto& d: fs::directory_iterator(Global::proc_path)){ + if (pread.is_open()) pread.close(); if (stop.load()) { procs.clear(); running.store(false); stop.store(false); return procs; } - numpids++; - pid_str = fs::path(d.path()).filename(); + pid_str = d.path().filename(); cpu = 0.0; cpu_s = 0.0; cpu_t = 0; cpu_n = 0; rss_mem = 0; threads = 0; state = '0'; ppid = 0; p_nice = 0; new_cache = false; if (d.is_directory() && isdigit(pid_str[0])) { + numpids++; pid = stoul(pid_str); c_pids.push_back(pid); @@ -172,76 +170,106 @@ namespace Proc { if (!cache.contains(pid)) { name.clear(); cmd.clear(); user.clear(); new_cache = true; - if (fs::exists((string)d.path() + "/comm")) { - pread.clear(); name.clear(); - ifstream pread((string)d.path() + "/comm"); - if (pread.good()) getline(pread, name); + pread.open(d.path() / "comm"); + if (pread.good()) { + getline(pread, name); pread.close(); } - if (fs::exists((string)d.path() + "/cmdline")) { - pread.clear(); cmd.clear(); tmpstr.clear(); - ifstream pread((string)d.path() + "/cmdline"); - if (pread.good()) while(getline(pread, tmpstr, '\0')) cmd += tmpstr + " "; + else continue; + + pread.open(d.path() / "cmdline"); + if (pread.good()) { + tmpstr.clear(); + 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"); - 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'); - } + else continue; + + pread.open(d.path() / "status"); + if (pread.good()) { + status.clear(); uid.clear(); + while (!pread.eof()){ + getline(pread, status, ':'); + if (status == "Uid") { + pread.ignore(); + getline(pread, uid, '\t'); + break; + } else { + pread.ignore(SSmax, '\n'); } } pread.close(); user = (!uid.empty() && uid_user.contains(uid)) ? uid_user.at(uid) : uid; } + else continue; cache[pid] = p_cache(name, cmd, user); } + //* Match filter if defined + if (!filter.empty() && pid_str.find(filter) == string::npos && + cache[pid].name.find(filter) == string::npos && + cache[pid].cmd.find(filter) == string::npos && + cache[pid].user.find(filter) == string::npos) { + if (new_cache) cache.erase(pid); + continue; + } + //* Get cpu usage, cpu cumulative and threads from /proc/[pid]/stat - if (fs::exists((string)d.path() + "/stat")) { - pread.clear(); instr.clear(); pstat.clear(); - ifstream pread((string)d.path() + "/stat"); - if (pread.good() && getline(pread, instr)) { - //? Skip pid and comm field and find comm fields closing ')' from right to avoid names with whitespace or parenthesis - pstat = ssplit(instr.substr(instr.rfind(')') + 1), " ", 37, true); - } + pread.open(d.path() / "stat"); + if (pread.good()) { + instr.clear(); s_pos = 0; c_pos = 0; s_count = 0; + getline(pread, instr); pread.close(); - if (pstat.size() < 20) continue; + //? Skip pid and comm field and find comm fields closing ')' to avoid names with whitespace or parenthesis + s_pos = instr.find_last_of(')') + 2; - //? Process state - state = pstat[0][0]; + do { + c_pos = instr.find(' ', s_pos); + if (c_pos == string::npos) break; - //? Process parent pid - ppid = stoul(pstat[1]); - - //? Process nice value - p_nice = stoi(pstat[16]); - - //? Process number of threads - threads = stoul(pstat[17]); - - //? Process utime + stime - cpu_t = stoull(pstat[11]) + stoull(pstat[12]); - - //? Cache cpu times and cpu seconds - if (new_cache) { - cache[pid].cpu_t = cpu_t; - cache[pid].cpu_s = stoull(pstat[19]); - } - - //? CPU number last executed on - if (pstat.size() > 36) cpu_n = stoi(pstat[36]); + switch (s_count) { + case 0: { //? Process state + state = instr[s_pos]; + break; + } + case 1: { //? Process parent pid + ppid = stoul(instr.substr(s_pos, c_pos - s_pos)); + break; + } + case 11: { //? Process utime + cpu_t = stoull(instr.substr(s_pos, c_pos - s_pos)); + break; + } + case 12: { //? Process stime + cpu_t += stoull(instr.substr(s_pos, c_pos - s_pos)); + break; + } + case 16: { //? Process nice value + p_nice = stoi(instr.substr(s_pos, c_pos - s_pos)); + break; + } + case 17: { //? Process number of threads + threads = stoul(instr.substr(s_pos, c_pos - s_pos)); + break; + } + case 19: { //? Cache cpu times and cpu seconds + if (new_cache) { + cache[pid].cpu_t = cpu_t; + cache[pid].cpu_s = stoull(instr.substr(s_pos, c_pos - s_pos)); + }; + break; + } + case 36: { //? CPU number last executed on + cpu_n = stoi(instr.substr(s_pos, c_pos - s_pos)); + break; + } + } + s_pos = c_pos + 1; + } while (s_count++ < 36); + if (s_count < 20) continue; //? Process cpu usage since last update, 100'000 because (100 percent * 1000 milliseconds) for correct conversion cpu = static_cast(100000 * (cpu_t - cache[pid].cpu_t) / since_last) / clk_tck; @@ -252,27 +280,17 @@ namespace Proc { //? Update cache with latest cpu times cache[pid].cpu_t = cpu_t; } + else continue; //* Get RSS memory in bytes from /proc/[pid]/statm - if (fs::exists((string)d.path() + "/statm")) { - pread.clear(); - ifstream pread((string)d.path() + "/statm"); - if (pread.good()) { - pread.ignore(numeric_limits::max(), ' '); - pread >> rss_mem; - rss_mem *= page_size; - } + pread.open(d.path() / "statm"); + if (pread.good()) { + pread.ignore(SSmax, ' '); + pread >> rss_mem; + rss_mem *= page_size; pread.close(); } - //* Match filter if defined - if (!filter.empty() && - pid_str.find(filter) == string::npos && - cache[pid].name.find(filter) == string::npos && - cache[pid].cmd.find(filter) == string::npos && - cache[pid].user.find(filter) == string::npos - ) continue; - //* Create proc_info procs.push_back(proc_info(pid, cache[pid].name, cache[pid].cmd, threads, cache[pid].user, rss_mem, cpu, cpu_s, state, cpu_n, p_nice, ppid)); } @@ -322,9 +340,8 @@ namespace Proc { //* Initialize needed variables for collect void init(){ - clk_tck = sysconf(_SC_CLK_TCK); tstamp = time_ms(); - passwd_path = (fs::exists(fs::path("/etc/passwd"))) ? fs::path("/etc/passwd") : passwd_path; + passwd_path = (access("/etc/passwd", R_OK) != -1) ? fs::path("/etc/passwd") : passwd_path; uint i = 0; for (auto& item : sort_array) sort_map[item] = i++; } diff --git a/src/btop_theme.h b/src/btop_theme.h index 98d23eb..12a2730 100644 --- a/src/btop_theme.h +++ b/src/btop_theme.h @@ -35,6 +35,51 @@ using namespace Tools; namespace Theme { + const unordered_map Default_theme = { + { "main_bg", "#00" }, + { "main_fg", "#cc" }, + { "title", "#ee" }, + { "hi_fg", "#969696" }, + { "selected_bg", "#7e2626" }, + { "selected_fg", "#ee" }, + { "inactive_fg", "#40" }, + { "graph_text", "#60" }, + { "meter_bg", "#40" }, + { "proc_misc", "#0de756" }, + { "cpu_box", "#3d7b46" }, + { "mem_box", "#8a882e" }, + { "net_box", "#423ba5" }, + { "proc_box", "#923535" }, + { "div_line", "#30" }, + { "temp_start", "#4897d4" }, + { "temp_mid", "#5474e8" }, + { "temp_end", "#ff40b6" }, + { "cpu_start", "#50f095" }, + { "cpu_mid", "#f2e266" }, + { "cpu_end", "#fa1e1e" }, + { "free_start", "#223014" }, + { "free_mid", "#b5e685" }, + { "free_end", "#dcff85" }, + { "cached_start", "#0b1a29" }, + { "cached_mid", "#74e6fc" }, + { "cached_end", "#26c5ff" }, + { "available_start", "#292107" }, + { "available_mid", "#ffd77a" }, + { "available_end", "#ffb814" }, + { "used_start", "#3b1f1c" }, + { "used_mid", "#d9626d" }, + { "used_end", "#ff4769" }, + { "download_start", "#231a63" }, + { "download_mid", "#4f43a3" }, + { "download_end", "#b0a9de" }, + { "upload_start", "#510554" }, + { "upload_mid", "#7d4180" }, + { "upload_end", "#dcafde" }, + { "process_start", "#80d0a3" }, + { "process_mid", "#dcd179" }, + { "process_end", "#d45454" } + }; + namespace { //* Convert 24-bit colors to 256 colors using 6x6x6 color cube int truecolor_to_256(uint r, uint g, uint b){ @@ -146,7 +191,7 @@ namespace Theme { vector t_rgb; string depth; colors.clear(); rgbs.clear(); - for (auto& [name, color] : Global::Default_theme) { + for (auto& [name, color] : Default_theme) { depth = (name.ends_with("bg")) ? "bg" : "fg"; if (source.contains(name)) { if (source.at(name)[0] == '#') { diff --git a/src/btop_tools.h b/src/btop_tools.h index 204e6b0..8a402fe 100644 --- a/src/btop_tools.h +++ b/src/btop_tools.h @@ -37,6 +37,21 @@ using std::string, std::vector, std::regex, std::max, std::to_string, std::cin; //? ------------------------------------------------- NAMESPACES ------------------------------------------------------ +namespace Symbols { + const string h_line = "─"; + const string v_line = "│"; + const string left_up = "┌"; + const string right_up = "┐"; + const string left_down = "└"; + const string right_down = "┘"; + const string title_left = "┤"; + const string title_right = "├"; + const string div_up = "┬"; + const string div_down = "┴"; + + const array superscript = { "⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹" }; +} + //* Collection of escape codes for text style and formatting namespace Fx { //* Escape sequence start @@ -228,34 +243,34 @@ namespace Tools { } //* Return current time since epoch in milliseconds - uint64_t time_ms(){ + inline uint64_t time_ms(){ return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); } //* Check if a string is a valid bool value - bool isbool(string str){ + inline bool isbool(string& str){ return (str == "true") || (str == "false") || (str == "True") || (str == "False"); } //* Check if a string is a valid integer value - bool isint(string str){ + inline bool isint(string& str){ return all_of(str.begin(), str.end(), ::isdigit); } //* Left-trim from and return string - string ltrim(string str, string t_str = " "){ + inline string ltrim(string str, string t_str = " "){ while (str.starts_with(t_str)) str.erase(0, t_str.size()); return str; } //* Right-trim from and return string - string rtrim(string str, string t_str = " "){ + inline string rtrim(string str, string t_str = " "){ while (str.ends_with(t_str)) str.resize(str.size() - t_str.size()); return str; } //* Left-right-trim from and return string - string trim(string str, string t_str = " "){ + inline string trim(string str, string t_str = " "){ return ltrim(rtrim(str, t_str), t_str); } @@ -352,6 +367,10 @@ namespace Tools { return out; } + //? Units for floating_humanizer function + const array Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"}; + const array Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"}; + //* 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 @@ -359,7 +378,7 @@ namespace Tools { 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; + auto& units = (bit) ? Units_bit : Units_byte; value *= 100 * mult;