mirror of
https://github.com/aristocratos/btop.git
synced 2024-05-18 19:33:03 +12:00
Added config file loader
This commit is contained in:
parent
63a286d6a1
commit
c4b55c7dfd
35
btop.cpp
35
btop.cpp
|
@ -256,8 +256,19 @@ int main(int argc, char **argv){
|
|||
}
|
||||
}
|
||||
|
||||
Global::debug = true;
|
||||
if (Global::debug) { Logger::loglevel = 4; Logger::debug("Starting in debug mode");}
|
||||
//? Read config file if present
|
||||
{ vector<string> load_errors;
|
||||
Config::load(Config::conf_file, load_errors);
|
||||
|
||||
if (Global::debug) Logger::loglevel = 4;
|
||||
else Logger::loglevel = v_index(Logger::log_levels, Config::getS("log_level"));
|
||||
|
||||
if (Logger::loglevel == 4) Logger::debug("Starting with logger set to debug.");
|
||||
|
||||
if (!load_errors.empty()) {
|
||||
for (auto& err_str : load_errors) Logger::error(err_str);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string(getenv("LANG")).ends_with("UTF-8") && !string(getenv("LANG")).ends_with("utf-8")) {
|
||||
string err_msg = "No UTF-8 locale was detected! Symbols might not look as intended.\n"
|
||||
|
@ -279,8 +290,7 @@ int main(int argc, char **argv){
|
|||
Proc::init();
|
||||
#endif
|
||||
|
||||
//? Read config file if present
|
||||
Config::load();
|
||||
|
||||
// Config::set("truecolor", false);
|
||||
|
||||
auto thts = time_ms();
|
||||
|
@ -433,6 +443,23 @@ int main(int argc, char **argv){
|
|||
}
|
||||
|
||||
|
||||
if (false) {
|
||||
cout << Config::getS("log_level") << endl;
|
||||
|
||||
vector<string> vv = {"hej", "vad", "du"};
|
||||
vector<int> vy;
|
||||
|
||||
cout << v_contains(vv, "vad"s) << endl;
|
||||
cout << v_index(vv, "hej"s) << endl;
|
||||
cout << v_index(vv, "du"s) << endl;
|
||||
cout << v_index(vv, "kodkod"s) << endl;
|
||||
cout << v_index(vy, 4) << endl;
|
||||
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
// if (thread_test){
|
||||
|
||||
// unordered_flat_map<int, future<string>> runners;
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace Config {
|
|||
|
||||
atomic<bool> locked (false);
|
||||
atomic<bool> writelock (false);
|
||||
bool changed;
|
||||
bool write_new;
|
||||
|
||||
vector<array<string, 2>> descriptions = {
|
||||
{"color_theme", "#* Color theme, looks for a .theme file in \"/usr/[local/]share/bpytop/themes\" and \"~/.config/bpytop/themes\", \"Default\" for builtin default theme.\n"
|
||||
|
@ -160,7 +160,7 @@ namespace Config {
|
|||
|
||||
bool _locked(){
|
||||
atomic_wait(writelock);
|
||||
if (!changed) changed = true;
|
||||
if (!write_new) write_new = true;
|
||||
return locked.load();
|
||||
}
|
||||
}
|
||||
|
@ -238,14 +238,71 @@ namespace Config {
|
|||
}
|
||||
|
||||
//* Load the config file from disk
|
||||
void load(){
|
||||
if (conf_file.empty()) return;
|
||||
else if (!fs::exists(conf_file)) { changed = true; return; }
|
||||
void load(fs::path conf_file, vector<string>& load_errors){
|
||||
if (conf_file.empty())
|
||||
return;
|
||||
else if (!fs::exists(conf_file)) {
|
||||
write_new = true;
|
||||
return;
|
||||
}
|
||||
std::ifstream cread(conf_file);
|
||||
if (cread.good()) {
|
||||
unordered_flat_map<string, int> valid_names;
|
||||
for (auto &n : descriptions)
|
||||
valid_names[n[0]] = 0;
|
||||
string v_string;
|
||||
getline(cread, v_string, '\n');
|
||||
if (!v_string.ends_with(Global::Version))
|
||||
write_new = true;
|
||||
while (!cread.eof()) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '#') {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
string name, value;
|
||||
getline(cread, name, '=');
|
||||
if (!valid_names.contains(name)) {
|
||||
cread.ignore(SSmax, '\n');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bools.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isbool(value))
|
||||
load_errors.push_back("Got an invalid bool value for config name: " + name);
|
||||
else
|
||||
bools.at(name) = stobool(value);
|
||||
}
|
||||
else if (ints.contains(name)) {
|
||||
cread >> value;
|
||||
if (!isint(value))
|
||||
load_errors.push_back("Got an invalid integer value for config name: " + name);
|
||||
else
|
||||
ints.at(name) = stoi(value);
|
||||
}
|
||||
else if (strings.contains(name)) {
|
||||
cread >> std::ws;
|
||||
if (cread.peek() == '"') {
|
||||
cread.ignore(1);
|
||||
getline(cread, value, '"');
|
||||
}
|
||||
else cread >> value;
|
||||
|
||||
if (name == "log_level" && !v_contains(Logger::log_levels, value)) load_errors.push_back("Invalid log_level: " + value);
|
||||
else strings.at(name) = value;
|
||||
}
|
||||
|
||||
cread.ignore(SSmax, '\n');
|
||||
}
|
||||
cread.close();
|
||||
if (!load_errors.empty()) write_new = true;
|
||||
}
|
||||
}
|
||||
|
||||
//* Write the config file to disk
|
||||
void write(){
|
||||
if (conf_file.empty() || !changed) return;
|
||||
if (conf_file.empty() || !write_new) return;
|
||||
Logger::debug("Writing new config file");
|
||||
std::ofstream cwrite(conf_file, std::ios::trunc);
|
||||
if (cwrite.good()) {
|
||||
|
|
|
@ -193,14 +193,13 @@ namespace Draw {
|
|||
int ai = 0;
|
||||
for (auto value : {last, data_value}) {
|
||||
if (value >= cur_high)
|
||||
result[ai] = 4;
|
||||
result[ai++] = 4;
|
||||
else if (value <= cur_low)
|
||||
result[ai] = 0;
|
||||
result[ai++] = 0;
|
||||
else {
|
||||
result[ai] = round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod);
|
||||
result[ai++] = round((float)(value - cur_low) * 4 / (cur_high - cur_low) + mod);
|
||||
if (no_zero && horizon == height - 1 && i != -1 && result[ai] == 0) result[ai] = 1;
|
||||
}
|
||||
ai++;
|
||||
}
|
||||
//? Generate braille symbol from 5x5 2D vector
|
||||
graphs[current][horizon] += (height == 1 && result[0] + result[1] == 0) ? Mv::r(1) : graph_symbol[(result[0] * 5 + result[1])];
|
||||
|
@ -228,8 +227,10 @@ namespace Draw {
|
|||
graphs[true].clear(); graphs[false].clear();
|
||||
this->width = width; this->height = height;
|
||||
this->invert = invert; this->offset = offset;
|
||||
this->no_zero = no_zero; this->max_value = max_value;
|
||||
this->no_zero = no_zero;
|
||||
this->color_gradient = color_gradient;
|
||||
if (max_value == 0 && offset > 0) max_value = 100;
|
||||
this->max_value = max_value;
|
||||
int value_width = ceil((float)data.size() / 2);
|
||||
int data_offset = 0;
|
||||
if (value_width > width) data_offset = data.size() - width * 2;
|
||||
|
@ -268,6 +269,7 @@ namespace Box {
|
|||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace Proc {
|
||||
|
|
|
@ -90,10 +90,10 @@ namespace Input {
|
|||
}
|
||||
|
||||
//* Get a key or mouse action from input
|
||||
string get(){
|
||||
string get(bool clear = false){
|
||||
string key;
|
||||
while (cin.rdbuf()->in_avail() > 0 && key.size() < 100) key += cin.get();
|
||||
if (!key.empty()){
|
||||
if (!clear && !key.empty()){
|
||||
if (key.substr(0,2) == Fx::e) key.erase(0, 1);
|
||||
if (Key_escapes.contains(key)) key = Key_escapes.at(key);
|
||||
else if (ulen(key) > 1) key = "";
|
||||
|
@ -105,9 +105,10 @@ namespace Input {
|
|||
//* Wait until input is available
|
||||
void wait(bool clear=false){
|
||||
while (cin.rdbuf()->in_avail() < 1) sleep_ms(10);
|
||||
if (clear) get();
|
||||
if (clear) get(clear);
|
||||
}
|
||||
|
||||
//* Clears last entered key
|
||||
void clear(){
|
||||
last.clear();
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ using std::string, std::vector, std::array, std::ifstream, std::atomic, std::num
|
|||
using std::cout, std::flush, std::endl;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Tools;
|
||||
const auto SSmax = std::numeric_limits<streamsize>::max();
|
||||
|
||||
//? --------------------------------------------------- FUNCTIONS -----------------------------------------------------
|
||||
|
||||
|
@ -63,6 +62,9 @@ namespace Proc {
|
|||
struct p_cache {
|
||||
string name, cmd, user;
|
||||
uint64_t cpu_t = 0, cpu_s = 0;
|
||||
string prefix = "";
|
||||
size_t depth = 0;
|
||||
bool collapsed = false;
|
||||
};
|
||||
unordered_flat_map<uint, p_cache> cache;
|
||||
unordered_flat_map<string, string> uid_user;
|
||||
|
@ -92,7 +94,7 @@ namespace Proc {
|
|||
};
|
||||
unordered_flat_map<string, int> sort_map;
|
||||
|
||||
//* proc_info: pid, name, cmd, threads, user, mem, cpu_p, cpu_c, state, cpu_n, p_nice, ppid
|
||||
//* Container for process information
|
||||
struct proc_info {
|
||||
uint pid;
|
||||
string name = "", cmd = "";
|
||||
|
@ -102,21 +104,25 @@ namespace Proc {
|
|||
double cpu_p = 0.0, cpu_c = 0.0;
|
||||
char state = '0';
|
||||
int cpu_n = 0, p_nice = 0;
|
||||
uint ppid = 0;
|
||||
int ppid = -1;
|
||||
string prefix = "";
|
||||
};
|
||||
|
||||
vector<proc_info> current_procs;
|
||||
|
||||
|
||||
//* Collects process information from /proc, saves to and returns reference to Proc::current_procs;
|
||||
auto& collect(string sorting="pid", bool reverse=false, string filter="", bool per_core=true){
|
||||
//* Collects and sorts process information from /proc, saves to and returns reference to Proc::current_procs;
|
||||
auto& collect(string sorting="pid", bool reverse=false, string filter="", bool per_core=true, bool tree=false){
|
||||
atomic_wait_set(collecting);
|
||||
ifstream pread;
|
||||
auto uptime = system_uptime();
|
||||
vector<proc_info> procs;
|
||||
vector<uint> pid_list;
|
||||
procs.reserve((numpids + 10));
|
||||
pid_list.reserve(numpids + 10);
|
||||
int npids = 0;
|
||||
int cmult = (per_core) ? Global::coreCount : 1;
|
||||
(void)tree;
|
||||
|
||||
//* Update uid_user map if /etc/passwd changed since last run
|
||||
if (!passwd_path.empty() && fs::last_write_time(passwd_path) != passwd_time) {
|
||||
|
@ -160,6 +166,7 @@ namespace Proc {
|
|||
if (d.is_directory() && isdigit(pid_str[0])) {
|
||||
npids++;
|
||||
proc_info new_proc (stoul(pid_str));
|
||||
pid_list.push_back(new_proc.pid);
|
||||
|
||||
//* Cache program name, command and username
|
||||
if (!cache.contains(new_proc.pid)) {
|
||||
|
@ -238,7 +245,7 @@ namespace Proc {
|
|||
break;
|
||||
}
|
||||
case 1: { //? Process parent pid
|
||||
new_proc.ppid = stoul(instr.substr(s_pos, c_pos - s_pos));
|
||||
new_proc.ppid = stoi(instr.substr(s_pos, c_pos - s_pos));
|
||||
break;
|
||||
}
|
||||
case 11: { //? Process utime
|
||||
|
@ -297,7 +304,7 @@ namespace Proc {
|
|||
}
|
||||
|
||||
|
||||
//* Sort processes vector
|
||||
//* Sort processes
|
||||
std::ranges::sort(procs, [sortint = sort_map.at(sorting), &reverse](proc_info& a, proc_info& b) {
|
||||
switch (sortint) {
|
||||
case 0: return (reverse) ? a.pid < b.pid : a.pid > b.pid;
|
||||
|
@ -326,14 +333,13 @@ namespace Proc {
|
|||
|
||||
//* Clear dead processes from cache at a regular interval
|
||||
if (++counter >= 10000 || ((int)cache.size() > npids + 100)) {
|
||||
unordered_flat_map<uint, p_cache> r_cache;
|
||||
r_cache.reserve(procs.size());
|
||||
counter = 0;
|
||||
if (filter.empty()) {
|
||||
for (auto& p : procs) r_cache[p.pid] = cache[p.pid];
|
||||
cache.swap(r_cache);
|
||||
unordered_flat_map<uint, p_cache> r_cache;
|
||||
r_cache.reserve(pid_list.size());
|
||||
for (auto& p : pid_list) {
|
||||
if (cache.contains(p)) r_cache[p] = cache.at(p);
|
||||
}
|
||||
else cache.clear();
|
||||
cache.swap(r_cache);
|
||||
}
|
||||
old_cputimes = cputimes;
|
||||
atomic_wait(drawing);
|
||||
|
|
|
@ -38,7 +38,7 @@ tab-size = 4
|
|||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
using std::string, std::vector, std::array, std::regex, std::max, std::to_string, std::cin, std::atomic, robin_hood::unordered_flat_map;
|
||||
using std::string, std::string_view, std::vector, std::array, std::regex, std::max, std::to_string, std::cin, std::atomic, robin_hood::unordered_flat_map;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
//? ------------------------------------------------- NAMESPACES ------------------------------------------------------
|
||||
|
@ -225,8 +225,10 @@ namespace Term {
|
|||
|
||||
namespace Tools {
|
||||
|
||||
const auto SSmax = std::numeric_limits<std::streamsize>::max();
|
||||
|
||||
//* Return number of UTF8 characters in a string with option to disregard escape sequences
|
||||
size_t ulen(string str, bool escape=false){
|
||||
size_t ulen(string str, const bool escape=false){
|
||||
if (escape) str = std::regex_replace(str, Fx::escape_regex, "");
|
||||
return std::count_if(str.begin(), str.end(),
|
||||
[](char c) { return (static_cast<unsigned char>(c) & 0xC0) != 0x80; } );
|
||||
|
@ -247,6 +249,18 @@ namespace Tools {
|
|||
return str;
|
||||
}
|
||||
|
||||
//* Check if vector <vec> contains value <find_val>
|
||||
template <typename T>
|
||||
bool v_contains(vector<T>& vec, T find_val) {
|
||||
return std::ranges::find(vec, find_val) != vec.end();
|
||||
}
|
||||
|
||||
//* Return index of <find_val> from vector <vec>, returns size of <vec> if <find_val> is not present
|
||||
template <typename T>
|
||||
size_t v_index(vector<T>& vec, T find_val) {
|
||||
return std::ranges::distance(vec.begin(), std::ranges::find(vec, find_val));
|
||||
}
|
||||
|
||||
//* Return current time since epoch in seconds
|
||||
uint64_t time_s(){
|
||||
return std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
@ -267,49 +281,57 @@ namespace Tools {
|
|||
return (str == "true") || (str == "false") || (str == "True") || (str == "False");
|
||||
}
|
||||
|
||||
//* Check if a string is a valid positive integer value
|
||||
bool isuint(string& str){
|
||||
return all_of(str.begin(), str.end(), ::isdigit);
|
||||
//* Convert string to bool, returning any value not equal to "true" or "True" as false
|
||||
bool stobool(string& str){
|
||||
return (str == "true" || str == "True") ? true : false;
|
||||
}
|
||||
|
||||
//* Left-trim <t_str> from <str> and return string
|
||||
string ltrim(string str, string t_str = " "){
|
||||
while (str.starts_with(t_str)) str.erase(0, t_str.size());
|
||||
return str;
|
||||
//* Check if a string is a valid integer value
|
||||
bool isint(string& str){
|
||||
if (str.empty()) return false;
|
||||
size_t offset = (str[0] == '-' ? 1 : 0);
|
||||
return all_of(str.begin() + offset, str.end(), ::isdigit);
|
||||
}
|
||||
|
||||
//* Right-trim <t_str> from <str> and return string
|
||||
string rtrim(string str, string t_str = " "){
|
||||
while (str.ends_with(t_str)) str.resize(str.size() - t_str.size());
|
||||
return str;
|
||||
//* Left-trim <t_str> from <str> and return new string
|
||||
string ltrim(const string& str, const string t_str = " "){
|
||||
string_view str_v = str;
|
||||
while (str_v.starts_with(t_str)) str_v.remove_prefix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
|
||||
//* Left-right-trim <t_str> from <str> and return string
|
||||
string trim(string str, string t_str = " "){
|
||||
//* Right-trim <t_str> from <str> and return new string
|
||||
string rtrim(const string& str, const string t_str = " "){
|
||||
string_view str_v = str;
|
||||
while (str_v.ends_with(t_str)) str_v.remove_suffix(t_str.size());
|
||||
return (string)str_v;
|
||||
}
|
||||
|
||||
//* Left-right-trim <t_str> from <str> and return new string
|
||||
string trim(const string& str, const string t_str = " "){
|
||||
return ltrim(rtrim(str, t_str), t_str);
|
||||
}
|
||||
|
||||
//* Split <string> at <delim> <time> number of times (0 for unlimited) and return vector
|
||||
vector<string> ssplit(string str, string delim = " ", int times = 0, bool ignore_remainder=false){
|
||||
vector<string> ssplit(const string& str, const string delim = " ", const int times = 0, const bool ignore_remainder=false){
|
||||
vector<string> out;
|
||||
string_view str_v = str;
|
||||
if (times > 0) out.reserve(times);
|
||||
if (!str.empty() && !delim.empty()){
|
||||
if (!str_v.empty() && !delim.empty()){
|
||||
size_t pos = 0;
|
||||
int x = 0;
|
||||
string tmp;
|
||||
while ((pos = str.find(delim)) != string::npos){
|
||||
tmp = str.substr(0, pos);
|
||||
if (tmp != delim && !tmp.empty()) out.push_back(tmp);
|
||||
str.erase(0, pos + delim.size());
|
||||
while ((pos = str_v.find(delim)) != string::npos){
|
||||
if (str_v.substr(0, pos) != delim) out.emplace_back(str_v.substr(0, pos));
|
||||
str_v.remove_prefix(pos + delim.size());
|
||||
if (times > 0 && ++x >= times) break;
|
||||
}
|
||||
}
|
||||
if (!ignore_remainder) out.push_back(str);
|
||||
if (!ignore_remainder) out.emplace_back(str_v);
|
||||
return out;
|
||||
}
|
||||
|
||||
//* Put current thread to sleep for <ms> milliseconds
|
||||
void sleep_ms(uint ms) {
|
||||
void sleep_ms(const uint& ms) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
}
|
||||
|
||||
|
@ -455,12 +477,12 @@ namespace Logger {
|
|||
std::atomic<bool> busy (false);
|
||||
bool first = true;
|
||||
string tdf = "%Y/%m/%d (%T) | ";
|
||||
unordered_flat_map<uint, string> log_levels = {
|
||||
{ 0, "DISABLED" },
|
||||
{ 1, "ERROR" },
|
||||
{ 2, "WARNING" },
|
||||
{ 3, "INFO" },
|
||||
{ 4, "DEBUG" }
|
||||
vector<string> log_levels = {
|
||||
"DISABLED",
|
||||
"ERROR",
|
||||
"WARNING",
|
||||
"INFO",
|
||||
"DEBUG"
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -480,7 +502,7 @@ namespace Logger {
|
|||
if (!ec) {
|
||||
std::ofstream lwrite(logfile, std::ios::app);
|
||||
if (first) { first = false; lwrite << "\n" << strf_time(tdf) << "===> btop++ v." << Global::Version << "\n";}
|
||||
lwrite << strf_time(tdf) << log_levels[level] << ": " << msg << "\n";
|
||||
lwrite << strf_time(tdf) << log_levels.at(level) << ": " << msg << "\n";
|
||||
lwrite.close();
|
||||
}
|
||||
else logfile.clear();
|
||||
|
|
Loading…
Reference in a new issue