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 <thread>
#include <future> #include <future>
#include <atomic> #include <atomic>
#include <filesystem>
#include <unistd.h> #include <unistd.h>
@ -35,26 +36,20 @@ tab-size = 4
#include <btop_theme.h> #include <btop_theme.h>
#if defined(__linux__) #if defined(__linux__)
#define SYSTEM "linux"
#include <btop_linux.h> #include <btop_linux.h>
#elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__) #elif defined(__unix__) || !defined(__APPLE__) && defined(__MACH__)
#include <sys/param.h> #include <sys/param.h>
#if defined(BSD) #if defined(BSD)
#define SYSTEM "bsd" // #include <btop_bsd.h>
#else
#define SYSTEM "unknown"
#endif #endif
#elif defined(__APPLE__) && defined(__MACH__) #elif defined(__APPLE__) && defined(__MACH__)
#include <TargetConditionals.h> #include <TargetConditionals.h>
#if TARGET_OS_MAC == 1 #if TARGET_OS_MAC == 1
#define SYSTEM "osx" // #include <btop_osx.h>
#else
#define SYSTEM "unknown"
#endif #endif
#else
#define SYSTEM "unknown"
#endif #endif
namespace fs = std::filesystem;
using namespace std; using namespace std;
@ -99,7 +94,7 @@ class C_Banner {
string banner_str; string banner_str;
public: public:
int width; int width = 0;
C_Banner(){ C_Banner(){
size_t z = 0; size_t z = 0;
@ -112,7 +107,7 @@ public:
fg = hex_to_color(line[0]); fg = hex_to_color(line[0]);
bg_i = 120-z*12; bg_i = 120-z*12;
bg = dec_to_color(bg_i, bg_i, bg_i); 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] == ' '){ if (line[1][i] == ' '){
letter = ' '; letter = ' ';
i -= 2; i -= 2;
@ -125,7 +120,7 @@ public:
oc = b_color; oc = b_color;
} }
z++; 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; 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); cout.setf(std::ios::boolalpha);
if (argc > 1) argumentParser(argc, argv); 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 //? Initialize terminal and set options
C_Term Term; C_Term Term;
if (!Term.initialized) { if (!Term.initialized) {
@ -174,7 +178,6 @@ int main(int argc, char **argv){
//? Initialize the Input class //? Initialize the Input class
C_Input Input; C_Input Input;
//* ------------------------------------------------ TESTING ------------------------------------------------------ //* ------------------------------------------------ TESTING ------------------------------------------------------
int debug = 2; int debug = 2;
@ -192,6 +195,7 @@ int main(int argc, char **argv){
//* Test MENUS //* Test MENUS
// for (auto& outer : Global::Menus){ // for (auto& outer : Global::Menus){
// for (auto& inner : outer.second){ // for (auto& inner : outer.second){
@ -264,10 +268,10 @@ int main(int argc, char **argv){
// insert Processes call here // insert Processes call here
uint lc;
unsigned lc;
string ostring; 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") { while (Input() != "q") {
timestamp = time_ms(); timestamp = time_ms();
@ -275,9 +279,10 @@ int main(int argc, char **argv){
timestamp = time_ms() - timestamp; timestamp = time_ms() - timestamp;
ostring.clear(); ostring.clear();
lc = 0; 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; (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 += (lcpu > 100) ? rjustify(to_string(lcpu), 3) + " " : rjustify(to_string(lcpu), 4);
ostring += "\n"; ostring += "\n";
if (lc++ > Term.height - 20) break; if (lc++ > Term.height - 20) break;

View File

@ -39,7 +39,7 @@ using namespace std;
namespace State { namespace State {
bool truecolor = true; bool truecolor = true;
string fg, bg; string fg, bg;
unsigned width, height; uint width, height;
} }
class C_Config { 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 #endif

View File

@ -37,23 +37,34 @@ tab-size = 4
namespace fs = std::filesystem; namespace fs = std::filesystem;
using namespace std; using namespace std;
namespace Global {
const string SYSTEM = "linux";
filesystem::path proc_path;
}
class Processes { class Processes {
uint64_t tstamp; uint64_t tstamp;
long int clk_tck; long int clk_tck;
map<int, tuple<string, string, string>> cache; map<int, tuple<string, string, string>> cache;
map<string, unsigned> sorts = { map<string, uint> sorts = {
{"pid", 0}, {"pid", 0},
{"name", 1}, {"name", 1},
{"command", 2}, {"command", 2},
{"user", 3}, {"threads", 3},
{"cpu direct", 4}, {"user", 4},
{"cpu lazy", 5} {"memory", 5},
{"cpu direct", 6},
{"cpu lazy", 7}
}; };
map<string, string> uid_user; map<string, string> uid_user;
fs::path passwd_path;
fs::file_time_type passwd_time; fs::file_time_type passwd_time;
map<int, uint64_t> cpu_times; map<int, uint64_t> cpu_times;
map<int, uint64_t> cpu_second; map<int, uint64_t> cpu_second;
unsigned counter = 0; uint counter = 0;
long page_size = sysconf(_SC_PAGE_SIZE);
public: public:
atomic<bool> stop; atomic<bool> stop;
atomic<bool> running; atomic<bool> running;
@ -62,40 +73,42 @@ public:
auto collect(string sorting="pid", bool reverse=false, string filter=""){ auto collect(string sorting="pid", bool reverse=false, string filter=""){
running.store(true); running.store(true);
int pid; int pid;
uint64_t cpu_t; uint64_t cpu_t, rss_mem;
double cpu, cpu_s; double cpu, cpu_s;
size_t threads;
ifstream pread; 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; auto since_last = time_ms() - tstamp;
if (since_last < 1) since_last = 1; if (since_last < 1) since_last = 1;
auto uptime = system_uptime(); auto uptime = system_uptime();
auto sortint = (sorts.contains(sorting)) ? sorts[sorting] : 5; auto sortint = (sorts.contains(sorting)) ? sorts[sorting] : 5;
vector<string> pstat; vector<string> pstat;
//? Return type! Values in tuple: pid, program, command, username, cpu%, cpu cumulative //? Return type! Values in tuple: pid, program, command, threads, username, mem KiB, cpu%, cpu cumulative
vector<tuple<int, string, string, string, double, double>> procs; vector<tuple<int, string, string, size_t, string, uint64_t, double, double>> procs;
//* Update uid_user map if /etc/passwd changed since last run //* 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; 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(); uid_user.clear();
pread.clear(); ifstream pread(passwd_path);
ifstream pread("/etc/passwd"); if (pread.good()) {
while (true){ while (true){
getline(pread, r_user, ':'); getline(pread, r_user, ':');
pread.ignore(numeric_limits<streamsize>::max(), ':'); pread.ignore(numeric_limits<streamsize>::max(), ':');
getline(pread, r_uid, ':'); getline(pread, r_uid, ':');
uid_user[r_uid] = r_user; uid_user[r_uid] = r_user;
pread.ignore(numeric_limits<streamsize>::max(), '\n'); pread.ignore(numeric_limits<streamsize>::max(), '\n');
if (pread.eof()) break; if (pread.eof()) break;
}
} }
pread.close(); pread.close();
} }
//* Iterate over all pid directories in /proc and get relevant values //* 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()) { if (stop.load()) {
procs.clear(); procs.clear();
running.store(false); running.store(false);
@ -104,16 +117,22 @@ public:
} }
pid_str = fs::path(d.path()).filename(); pid_str = fs::path(d.path()).filename();
cpu = 0.0; cpu = 0.0;
rss_mem = 0;
if (d.is_directory() && isdigit(pid_str[0])) { if (d.is_directory() && isdigit(pid_str[0])) {
pid = stoi(pid_str); pid = stoi(pid_str);
//* Get cpu usage //* Get cpu usage, threads and rss mem from [pid]/stat
if (fs::is_regular_file((string)d.path() + "/stat")) { if (fs::exists((string)d.path() + "/stat")) {
pstat.clear(); pread.clear(); pstat.clear();
ifstream pread((string)d.path() + "/stat"); 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(); pread.close();
if (pstat.size() < 37) continue;
//? Process number of threads
threads = stoul(pstat[19]);
//? Process utime + stime //? Process utime + stime
cpu_t = stoull(pstat[13]) + stoull(pstat[14]); cpu_t = stoull(pstat[13]) + stoull(pstat[14]);
if (!cpu_times.contains(pid)) cpu_times[pid] = cpu_t; if (!cpu_times.contains(pid)) cpu_times[pid] = cpu_t;
@ -121,6 +140,9 @@ public:
//? Cache process start time //? Cache process start time
if (!cpu_second.contains(pid)) cpu_second[pid] = stoull(pstat[21]); 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 //? 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; cpu = static_cast<double>(100000 * (cpu_t - cpu_times[pid]) / since_last) / clk_tck;
@ -129,86 +151,106 @@ public:
cpu_times[pid] = cpu_t; 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 //* Cache program name, command and username
if (!cache.contains(pid)) { 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"); ifstream pread((string)d.path() + "/comm");
getline(pread, name); if (pread.good()) getline(pread, name);
pread.close(); pread.close();
} }
if (fs::is_regular_file((string)d.path() + "/cmdline")) { if (fs::exists((string)d.path() + "/cmdline")) {
cmd.clear(); pread.clear(); cmd.clear(); tmpstr.clear();
ifstream pread((string)d.path() + "/cmdline"); 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(); pread.close();
if (!cmd.empty()) cmd.pop_back(); if (!cmd.empty()) cmd.pop_back();
} }
if (fs::exists((string)d.path() + "/status")) { if (fs::exists((string)d.path() + "/status")) {
pread.clear(); status.clear(); uid.clear();
ifstream pread((string)d.path() + "/status"); ifstream pread((string)d.path() + "/status");
status.clear(); if (pread.good()) {
while (!pread.eof()){ while (!pread.eof()){
getline(pread, status, ':'); getline(pread, status, ':');
if (status == "Uid") { if (status == "Uid") {
pread.ignore(); pread.ignore();
getline(pread, uid, '\t'); getline(pread, uid, '\t');
break; break;
} else { } else {
pread.ignore(numeric_limits<streamsize>::max(), '\n'); pread.ignore(numeric_limits<streamsize>::max(), '\n');
}
} }
} }
pread.close(); 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); cache[pid] = make_tuple(name, cmd, user);
} }
// //* Match filter if applicable // //* Match filter if applicable
if (!filter.empty() && if (!filter.empty() &&
pid_str.find(filter) == string::npos && pid_str.find(filter) == string::npos && //? Pid
get<0>(cache[pid]).find(filter) == string::npos && get<0>(cache[pid]).find(filter) == string::npos && //? Program
get<1>(cache[pid]).find(filter) == string::npos && get<1>(cache[pid]).find(filter) == string::npos && //? Command
get<2>(cache[pid]).find(filter) == string::npos get<2>(cache[pid]).find(filter) == string::npos //? User
) continue; ) continue;
//* Create tuple //* 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(); // auto st = time_ms();
//* Sort processes vector //* 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) { switch (sortint) {
case 0: return (reverse) ? get<0>(a) < get<0>(b) : get<0>(a) > get<0>(b); //? Pid 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 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 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 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); //? Cpu direct 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); //? Cpu lazy 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; return false;
} }
); );
//* When using "cpu lazy" sorting, push processes with high cpu usage to the front regardless of cumulative usage //* When using "cpu lazy" sorting push processes with high cpu usage to the front regardless of cumulative usage
if (sortint == 5 && !reverse) { if (sortint == 6 && !reverse) {
double max = 10.0, target = 30.0; double max = 10.0, target = 30.0;
for (size_t i = 0, offset = 0; i < procs.size(); i++) { 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; else if (i == 6) target = (max > 30.0) ? max : 10.0;
if (i == offset && get<4>(procs[i]) > 30.0) offset++; if (i == offset && get<6>(procs[i]) > 30.0) offset++;
else if (get<4>(procs[i]) > target) rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1); else if (get<6>(procs[i]) > target) rotate(procs.begin() + offset, procs.begin() + i, procs.begin() + i + 1);
} }
} }
//* Clear all cached values at a regular interval to get rid of dead processes
// 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)) { if (++counter >= 10000 || (filter.empty() && cache.size() > procs.size() + 100)) {
counter = 0; counter = 0;
cache.clear(); cache.clear();
@ -225,6 +267,7 @@ public:
clk_tck = sysconf(_SC_CLK_TCK); clk_tck = sysconf(_SC_CLK_TCK);
tstamp = time_ms(); tstamp = time_ms();
stop.store(false); stop.store(false);
passwd_path = (fs::exists(fs::path("/etc/passwd"))) ? fs::path("/etc/passwd") : passwd_path;
collect(); collect();
} }
}; };

View File

@ -33,7 +33,7 @@ using namespace std;
//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
//* Convert 24-bit colors to 256 colors using 6x6x6 color cube //* 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) { if (r / 11 == g / 11 && g / 11 == b / 11) {
return 232 + r / 11; return 232 + r / 11;
} else { } 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;"; pre += (t_to_256) ? "5;" : "2;";
if (hexa.size() == 2){ if (hexa.size() == 2){
unsigned h_int = stoi(hexa, 0, 16); uint h_int = stoi(hexa, 0, 16);
if (t_to_256){ if (t_to_256){
return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m"; return pre + to_string(truecolor_to_256(h_int, h_int, h_int)) + "m";
} else { } 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] //* Args r: [0-255], g: [0-255], b: [0-255]
//* t_to_256: [true|false] convert 24bit value to 256 color value //* t_to_256: [true|false] convert 24bit value to 256 color value
//* depth: ["fg"|"bg"] for either a foreground color or a background color //* 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"; depth = (depth == "fg") ? "38" : "48";
string pre = Fx::e + depth + ";"; string pre = Fx::e + depth + ";";
pre += (t_to_256) ? "5;" : "2;"; pre += (t_to_256) ? "5;" : "2;";

View File

@ -42,7 +42,7 @@ using namespace std;
//* Collection of escape codes for text style and formatting //* Collection of escape codes for text style and formatting
namespace Fx { namespace Fx {
//* Escape sequence start //* Escape sequence start
const string e = "\x1b["; const string e = "\033[";
//* Bold on //* Bold on
const string b = e + "1m"; const string b = e + "1m";
@ -96,9 +96,9 @@ namespace Mv {
//? --------------------------------------------------- FUNCTIONS ----------------------------------------------------- //? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
//* Return number of UTF8 characters in a string //* 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(), 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 //* 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 //* 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)); std::this_thread::sleep_for(std::chrono::milliseconds(ms));
} }
@ -207,9 +207,9 @@ string trans(string str){
return (newstr.empty()) ? str : newstr; return (newstr.empty()) ? str : newstr;
} }
string sec_to_dhms(unsigned sec){ string sec_to_dhms(uint sec){
string out; string out;
unsigned d, h, m; uint d, h, m;
d = sec / (3600 * 24); d = sec / (3600 * 24);
sec %= 3600 * 24; sec %= 3600 * 24;
h = sec / 3600; h = sec / 3600;
@ -231,6 +231,41 @@ double system_uptime(){
return stod(upstr); 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 ----------------------------------------------------- //? --------------------------------------------------- CLASSES -----------------------------------------------------
@ -240,8 +275,8 @@ class C_Term {
public: public:
bool initialized = false; bool initialized = false;
bool resized = false; bool resized = false;
unsigned width = 0; uint width = 0;
unsigned height = 0; uint height = 0;
//* Hide terminal cursor //* Hide terminal cursor
const string hide_cursor = Fx::e + "?25l"; const string hide_cursor = Fx::e + "?25l";