Added threads, memory to processes

This commit is contained in:
aristocratos 2021-05-13 21:11:10 +02:00
parent 621534f5d4
commit 05eb21dfbb
6 changed files with 179 additions and 91 deletions

View File

@ -25,6 +25,7 @@ tab-size = 4
#include <thread>
#include <future>
#include <atomic>
#include <filesystem>
#include <unistd.h>
@ -35,26 +36,20 @@ tab-size = 4
#include <btop_theme.h>
#if defined(__linux__)
#define SYSTEM "linux"
#include <btop_linux.h>
#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
#include <sys/param.h>
#if defined(BSD)
#define SYSTEM "bsd"
#else
#define SYSTEM "unknown"
// #include <btop_bsd.h>
#endif
#elif defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h>
#if TARGET_OS_MAC == 1
#define SYSTEM "osx"
#else
#define SYSTEM "unknown"
// #include <btop_osx.h>
#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;

View File

@ -39,7 +39,7 @@ using namespace std;
namespace State {
bool truecolor = true;
string fg, bg;
unsigned width, height;
uint width, height;
}
class C_Config {

View File

@ -113,6 +113,11 @@ namespace Global {
} }
} }
};
//? Units for floating_humanizer function
const vector<string> Units_bit = {"bit", "Kib", "Mib", "Gib", "Tib", "Pib", "Eib", "Zib", "Yib", "Bib", "GEb"};
const vector<string> Units_byte = {"Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "BiB", "GEB"};
}
#endif

View File

@ -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<int, tuple<string, string, string>> cache;
map<string, unsigned> sorts = {
map<string, uint> 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<string, string> uid_user;
fs::path passwd_path;
fs::file_time_type passwd_time;
map<int, uint64_t> cpu_times;
map<int, uint64_t> cpu_second;
unsigned counter = 0;
uint counter = 0;
long page_size = sysconf(_SC_PAGE_SIZE);
public:
atomic<bool> stop;
atomic<bool> 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<string> pstat;
//? Return type! Values in tuple: pid, program, command, username, cpu%, cpu cumulative
vector<tuple<int, string, string, string, double, double>> procs;
//? Return type! Values in tuple: pid, program, command, threads, username, mem KiB, cpu%, cpu cumulative
vector<tuple<int, string, string, size_t, string, uint64_t, double, double>> 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<streamsize>::max(), ':');
getline(pread, r_uid, ':');
uid_user[r_uid] = r_user;
pread.ignore(numeric_limits<streamsize>::max(), '\n');
if (pread.eof()) break;
ifstream pread(passwd_path);
if (pread.good()) {
while (true){
getline(pread, r_user, ':');
pread.ignore(numeric_limits<streamsize>::max(), ':');
getline(pread, r_uid, ':');
uid_user[r_uid] = r_user;
pread.ignore(numeric_limits<streamsize>::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<double>(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<streamsize>::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<streamsize>::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<streamsize>::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<int, string, string, string, double, double>& a, tuple<int, string, string, string, double, double>& b) {
ranges::sort(procs, [&sortint, &reverse]( tuple<int, string, string, size_t, string, uint64_t, double, double>& a,
tuple<int, string, string, size_t, string, uint64_t, double, double>& 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();
}
};

View File

@ -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;";

View File

@ -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<unsigned char>(c) & 0xC0) != 0x80; } );
[](char& c) { return (c & 0xC0) != 0x80; } );
}
//* Return current time since epoch in milliseconds
@ -152,7 +152,7 @@ vector<string> ssplit(string str, string delim = " ", int times = 0){
}
//* Put current thread to sleep for <ms> 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";