| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188 |
- /* Copyright 2021 Aristocratos (jakob@qvantnet.com)
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- indent = tab
- tab-size = 4
- */
- #include "btop.hpp"
- #include <algorithm>
- #include <csignal>
- #include <clocale>
- #include <filesystem>
- #include <iterator>
- #include <optional>
- #include <pthread.h>
- #include <span>
- #include <string_view>
- #ifdef __FreeBSD__
- #include <pthread_np.h>
- #endif
- #include <thread>
- #include <numeric>
- #include <ranges>
- #include <unistd.h>
- #include <cmath>
- #include <iostream>
- #include <exception>
- #include <tuple>
- #include <regex>
- #include <chrono>
- #include <utility>
- #include <semaphore>
- #ifdef __APPLE__
- #include <CoreFoundation/CoreFoundation.h>
- #include <mach-o/dyld.h>
- #include <limits.h>
- #endif
- #ifdef __NetBSD__
- #include <sys/param.h>
- #include <sys/sysctl.h>
- #include <unistd.h>
- #endif
- #include "btop_cli.hpp"
- #include "btop_shared.hpp"
- #include "btop_tools.hpp"
- #include "btop_config.hpp"
- #include "btop_input.hpp"
- #include "btop_theme.hpp"
- #include "btop_draw.hpp"
- #include "btop_menu.hpp"
- #include "fmt/core.h"
- #include "fmt/ostream.h"
- using std::atomic;
- using std::cout;
- using std::flush;
- using std::min;
- using std::string;
- using std::string_view;
- using std::to_string;
- using std::vector;
- namespace fs = std::filesystem;
- using namespace Tools;
- using namespace std::chrono_literals;
- using namespace std::literals;
- namespace Global {
- const vector<array<string, 2>> Banner_src = {
- {"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
- {"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
- {"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
- {"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
- {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
- {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
- };
- const string Version = "1.4.5";
- int coreCount;
- string overlay;
- string clock;
- string bg_black = "\x1b[0;40m";
- string fg_white = "\x1b[1;97m";
- string fg_green = "\x1b[1;92m";
- string fg_red = "\x1b[0;91m";
- uid_t real_uid, set_uid;
- fs::path self_path;
- string exit_error_msg;
- atomic<bool> thread_exception (false);
- bool debug{};
- uint64_t start_time;
- atomic<bool> resized (false);
- atomic<bool> quitting (false);
- atomic<bool> should_quit (false);
- atomic<bool> should_sleep (false);
- atomic<bool> _runner_started (false);
- atomic<bool> init_conf (false);
- atomic<bool> reload_conf (false);
- }
- //* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
- void term_resize(bool force) {
- static atomic<bool> resizing (false);
- if (Input::polling) {
- Global::resized = true;
- Input::interrupt();
- return;
- }
- atomic_lock lck(resizing, true);
- if (auto refreshed = Term::refresh(true); refreshed or force) {
- if (force and refreshed) force = false;
- }
- else return;
- #ifdef GPU_SUPPORT
- static const array<string, 10> all_boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"};
- #else
- static const array<string, 5> all_boxes = {"", "cpu", "mem", "net", "proc"};
- #endif
- Global::resized = true;
- if (Runner::active) Runner::stop();
- Term::refresh();
- Config::unlock();
- auto boxes = Config::getS("shown_boxes");
- auto min_size = Term::get_min_size(boxes);
- auto minWidth = min_size.at(0), minHeight = min_size.at(1);
- while (not force or (Term::width < minWidth or Term::height < minHeight)) {
- sleep_ms(100);
- if (Term::width < minWidth or Term::height < minHeight) {
- int width = Term::width, height = Term::height;
- cout << fmt::format("{clear}{bg_black}{fg_white}"
- "{mv1}Terminal size too small:"
- "{mv2} Width = {fg_width}{width} {fg_white}Height = {fg_height}{height}"
- "{mv3}{fg_white}Needed for current config:"
- "{mv4}Width = {minWidth} Height = {minHeight}",
- "clear"_a = Term::clear, "bg_black"_a = Global::bg_black, "fg_white"_a = Global::fg_white,
- "mv1"_a = Mv::to((height / 2) - 2, (width / 2) - 11),
- "mv2"_a = Mv::to((height / 2) - 1, (width / 2) - 10),
- "fg_width"_a = (width < minWidth ? Global::fg_red : Global::fg_green),
- "width"_a = width,
- "fg_height"_a = (height < minHeight ? Global::fg_red : Global::fg_green),
- "height"_a = height,
- "mv3"_a = Mv::to((height / 2) + 1, (width / 2) - 12),
- "mv4"_a = Mv::to((height / 2) + 2, (width / 2) - 10),
- "minWidth"_a = minWidth,
- "minHeight"_a = minHeight
- ) << std::flush;
- bool got_key = false;
- for (; not Term::refresh() and not got_key; got_key = Input::poll(10));
- if (got_key) {
- auto key = Input::get();
- if (key == "q")
- clean_quit(0);
- else if (key.size() == 1 and isint(key)) {
- auto intKey = stoi(key);
- #ifdef GPU_SUPPORT
- if ((intKey == 0 and Gpu::count >= 5) or (intKey >= 5 and intKey - 4 <= Gpu::count)) {
- #else
- if (intKey > 0 and intKey < 5) {
- #endif
- const auto& box = all_boxes.at(intKey);
- Config::current_preset = -1;
- Config::toggle_box(box);
- boxes = Config::getS("shown_boxes");
- }
- }
- }
- min_size = Term::get_min_size(boxes);
- minWidth = min_size.at(0);
- minHeight = min_size.at(1);
- }
- else if (not Term::refresh()) break;
- }
- Input::interrupt();
- }
- //* Exit handler; stops threads, restores terminal and saves config changes
- void clean_quit(int sig) {
- if (Global::quitting) return;
- Global::quitting = true;
- Runner::stop();
- if (Global::_runner_started) {
- #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__
- if (pthread_join(Runner::runner_id, nullptr) != 0) {
- Logger::warning("Failed to join _runner thread on exit!");
- pthread_cancel(Runner::runner_id);
- }
- #else
- constexpr struct timespec ts { .tv_sec = 5, .tv_nsec = 0 };
- if (pthread_timedjoin_np(Runner::runner_id, nullptr, &ts) != 0) {
- Logger::warning("Failed to join _runner thread on exit!");
- pthread_cancel(Runner::runner_id);
- }
- #endif
- }
- #ifdef GPU_SUPPORT
- Gpu::Nvml::shutdown();
- Gpu::Rsmi::shutdown();
- #endif
- Config::write();
- if (Term::initialized) {
- Input::clear();
- Term::restore();
- }
- if (not Global::exit_error_msg.empty()) {
- sig = 1;
- Logger::error(Global::exit_error_msg);
- fmt::println(std::cerr, "{}ERROR: {}{}{}", Global::fg_red, Global::fg_white, Global::exit_error_msg, Fx::reset);
- }
- Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
- const auto excode = (sig != -1 ? sig : 0);
- #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__
- _Exit(excode);
- #else
- quick_exit(excode);
- #endif
- }
- //* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
- static void _sleep() {
- Runner::stop();
- Term::restore();
- std::raise(SIGSTOP);
- }
- //* Handler for SIGCONT; re-initialize terminal and force a resize event
- static void _resume() {
- Term::init();
- term_resize(true);
- }
- static void _exit_handler() {
- clean_quit(-1);
- }
- static void _crash_handler(const int sig) {
- // Restore terminal before crashing
- if (Term::initialized) {
- Term::restore();
- }
- // Re-raise the signal to get default behavior (core dump)
- std::signal(sig, SIG_DFL);
- std::raise(sig);
- }
- static void _signal_handler(const int sig) {
- switch (sig) {
- case SIGINT:
- if (Runner::active) {
- Global::should_quit = true;
- Runner::stopping = true;
- Input::interrupt();
- }
- else {
- clean_quit(0);
- }
- break;
- case SIGTSTP:
- if (Runner::active) {
- Global::should_sleep = true;
- Runner::stopping = true;
- Input::interrupt();
- }
- else {
- _sleep();
- }
- break;
- case SIGCONT:
- _resume();
- break;
- case SIGWINCH:
- term_resize();
- break;
- case SIGUSR1:
- // Input::poll interrupt
- break;
- case SIGUSR2:
- Global::reload_conf = true;
- Input::interrupt();
- break;
- }
- }
- //* Config init
- void init_config(bool low_color, std::optional<std::string>& filter) {
- atomic_lock lck(Global::init_conf);
- vector<string> load_warnings;
- Config::load(Config::conf_file, load_warnings);
- Config::set("lowcolor", (low_color ? true : not Config::getB("truecolor")));
- static bool first_init = true;
- if (Global::debug and first_init) {
- Logger::set("DEBUG");
- Logger::debug("Running in DEBUG mode!");
- }
- else Logger::set(Config::getS("log_level"));
- if (filter.has_value()) {
- Config::set("proc_filter", filter.value());
- }
- static string log_level;
- if (const string current_level = Config::getS("log_level"); log_level != current_level) {
- log_level = current_level;
- Logger::info("Logger set to " + (Global::debug ? "DEBUG" : log_level));
- }
- for (const auto& err_str : load_warnings) Logger::warning(err_str);
- first_init = false;
- }
- //* Manages secondary thread for collection and drawing of boxes
- namespace Runner {
- atomic<bool> active (false);
- atomic<bool> stopping (false);
- atomic<bool> waiting (false);
- atomic<bool> redraw (false);
- atomic<bool> coreNum_reset (false);
- //* Setup semaphore for triggering thread to do work
- // TODO: This can be made a local without too much effort.
- std::binary_semaphore do_work { 0 };
- inline void thread_wait() { do_work.acquire(); }
- inline void thread_trigger() { do_work.release(); }
- //* RAII wrapper for pthread_mutex locking
- class thread_lock {
- pthread_mutex_t& pt_mutex;
- public:
- int status;
- explicit thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) {
- pthread_mutex_init(&pt_mutex, nullptr);
- status = pthread_mutex_lock(&pt_mutex);
- }
- ~thread_lock() noexcept {
- if (status == 0)
- pthread_mutex_unlock(&pt_mutex);
- }
- thread_lock(const thread_lock& other) = delete;
- thread_lock& operator=(const thread_lock& other) = delete;
- thread_lock(thread_lock&& other) = delete;
- thread_lock& operator=(thread_lock&& other) = delete;
- };
- //* Wrapper for raising privileges when using SUID bit
- class gain_priv {
- int status = -1;
- public:
- gain_priv() {
- if (Global::real_uid != Global::set_uid)
- this->status = seteuid(Global::set_uid);
- }
- ~gain_priv() noexcept {
- if (status == 0)
- status = seteuid(Global::real_uid);
- }
- gain_priv(const gain_priv& other) = delete;
- gain_priv& operator=(const gain_priv& other) = delete;
- gain_priv(gain_priv&& other) = delete;
- gain_priv& operator=(gain_priv&& other) = delete;
- };
- string output;
- string empty_bg;
- bool pause_output{};
- sigset_t mask;
- pthread_t runner_id;
- pthread_mutex_t mtx;
- enum debug_actions {
- collect_begin,
- collect_done,
- draw_begin,
- draw_begin_only,
- draw_done
- };
- enum debug_array {
- collect,
- draw
- };
- string debug_bg;
- std::unordered_map<string, array<uint64_t, 2>> debug_times;
- class MyNumPunct : public std::numpunct<char>
- {
- protected:
- virtual char do_thousands_sep() const override { return '\''; }
- virtual std::string do_grouping() const override { return "\03"; }
- };
- struct runner_conf {
- vector<string> boxes;
- bool no_update;
- bool force_redraw;
- bool background_update;
- string overlay;
- string clock;
- };
- struct runner_conf current_conf;
- static void debug_timer(const char* name, const int action) {
- switch (action) {
- case collect_begin:
- debug_times[name].at(collect) = time_micros();
- return;
- case collect_done:
- debug_times[name].at(collect) = time_micros() - debug_times[name].at(collect);
- debug_times["total"].at(collect) += debug_times[name].at(collect);
- return;
- case draw_begin_only:
- debug_times[name].at(draw) = time_micros();
- return;
- case draw_begin:
- debug_times[name].at(draw) = time_micros();
- debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect);
- debug_times["total"].at(collect) += debug_times[name].at(collect);
- return;
- case draw_done:
- debug_times[name].at(draw) = time_micros() - debug_times[name].at(draw);
- debug_times["total"].at(draw) += debug_times[name].at(draw);
- return;
- }
- }
- //? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
- static void * _runner(void *) {
- //? Block some signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
- sigemptyset(&mask);
- // sigaddset(&mask, SIGINT);
- // sigaddset(&mask, SIGTSTP);
- sigaddset(&mask, SIGWINCH);
- sigaddset(&mask, SIGTERM);
- pthread_sigmask(SIG_BLOCK, &mask, nullptr);
- //? pthread_mutex_lock to lock thread and monitor health from main thread
- thread_lock pt_lck(mtx);
- if (pt_lck.status != 0) {
- Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
- Global::thread_exception = true;
- Input::interrupt();
- stopping = true;
- }
- //* ----------------------------------------------- THREAD LOOP -----------------------------------------------
- while (not Global::quitting) {
- thread_wait();
- atomic_wait_for(active, true, 5000);
- if (active) {
- Global::exit_error_msg = "Runner thread failed to get active lock!";
- Global::thread_exception = true;
- Input::interrupt();
- stopping = true;
- }
- if (stopping or Global::resized) {
- sleep_ms(1);
- continue;
- }
- //? Atomic lock used for blocking non thread-safe actions in main thread
- atomic_lock lck(active);
- //? Set effective user if SUID bit is set
- gain_priv powers{};
- auto& conf = current_conf;
- //! DEBUG stats
- if (Global::debug) {
- if (debug_bg.empty() or redraw)
- Runner::debug_bg = Draw::createBox(2, 2, 33,
- #ifdef GPU_SUPPORT
- 9,
- #else
- 8,
- #endif
- "", true, "μs");
- debug_times.clear();
- debug_times["total"] = {0, 0};
- }
- output.clear();
- //* Run collection and draw functions for all boxes
- try {
- #ifdef GPU_SUPPORT
- //? GPU data collection
- const bool gpu_in_cpu_panel = Gpu::gpu_names.size() > 0 and (
- Config::getS("cpu_graph_lower").starts_with("gpu-")
- or (Config::getS("cpu_graph_lower") == "Auto")
- or Config::getS("cpu_graph_upper").starts_with("gpu-")
- or (Gpu::shown == 0 and Config::getS("show_gpu_info") != "Off")
- );
- vector<unsigned int> gpu_panels = {};
- for (auto& box : conf.boxes)
- if (box.starts_with("gpu"))
- gpu_panels.push_back(box.back()-'0');
- vector<Gpu::gpu_info> gpus;
- if (gpu_in_cpu_panel or not gpu_panels.empty()) {
- if (Global::debug) debug_timer("gpu", collect_begin);
- gpus = Gpu::collect(conf.no_update);
- if (Global::debug) debug_timer("gpu", collect_done);
- }
- auto& gpus_ref = gpus;
- #else
- vector<Gpu::gpu_info> gpus_ref{};
- #endif
- //? CPU
- if (v_contains(conf.boxes, "cpu")) {
- try {
- if (Global::debug) debug_timer("cpu", collect_begin);
- //? Start collect
- auto cpu = Cpu::collect(conf.no_update);
- if (coreNum_reset) {
- coreNum_reset = false;
- Cpu::core_mapping = Cpu::get_core_mapping();
- Global::resized = true;
- Input::interrupt();
- continue;
- }
- if (Global::debug) debug_timer("cpu", draw_begin);
- //? Draw box
- if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update);
- if (Global::debug) debug_timer("cpu", draw_done);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Cpu:: -> " + string{e.what()});
- }
- }
- #ifdef GPU_SUPPORT
- //? GPU
- if (not gpu_panels.empty() and not gpus_ref.empty()) {
- try {
- if (Global::debug) debug_timer("gpu", draw_begin_only);
- //? Draw box
- if (not pause_output)
- for (unsigned long i = 0; i < gpu_panels.size(); ++i)
- output += Gpu::draw(gpus_ref[gpu_panels[i]], i, conf.force_redraw, conf.no_update);
- if (Global::debug) debug_timer("gpu", draw_done);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Gpu:: -> " + string{e.what()});
- }
- }
- #endif
- //? MEM
- if (v_contains(conf.boxes, "mem")) {
- try {
- if (Global::debug) debug_timer("mem", collect_begin);
- //? Start collect
- auto mem = Mem::collect(conf.no_update);
- if (Global::debug) debug_timer("mem", draw_begin);
- //? Draw box
- if (not pause_output) output += Mem::draw(mem, conf.force_redraw, conf.no_update);
- if (Global::debug) debug_timer("mem", draw_done);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Mem:: -> " + string{e.what()});
- }
- }
- //? NET
- if (v_contains(conf.boxes, "net")) {
- try {
- if (Global::debug) debug_timer("net", collect_begin);
- //? Start collect
- auto net = Net::collect(conf.no_update);
- if (Global::debug) debug_timer("net", draw_begin);
- //? Draw box
- if (not pause_output) output += Net::draw(net, conf.force_redraw, conf.no_update);
- if (Global::debug) debug_timer("net", draw_done);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Net:: -> " + string{e.what()});
- }
- }
- //? PROC
- if (v_contains(conf.boxes, "proc")) {
- try {
- if (Global::debug) debug_timer("proc", collect_begin);
- //? Start collect
- auto proc = Proc::collect(conf.no_update);
- if (Global::debug) debug_timer("proc", draw_begin);
- //? Draw box
- if (not pause_output) output += Proc::draw(proc, conf.force_redraw, conf.no_update);
- if (Global::debug) debug_timer("proc", draw_done);
- }
- catch (const std::exception& e) {
- throw std::runtime_error("Proc:: -> " + string{e.what()});
- }
- }
- }
- catch (const std::exception& e) {
- Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()};
- Global::thread_exception = true;
- Input::interrupt();
- stopping = true;
- }
- if (stopping) {
- continue;
- }
- if (redraw or conf.force_redraw) {
- empty_bg.clear();
- redraw = false;
- }
- if (not pause_output) output += conf.clock;
- if (not conf.overlay.empty() and not conf.background_update) pause_output = true;
- if (output.empty() and not pause_output) {
- if (empty_bg.empty()) {
- const int x = Term::width / 2 - 10, y = Term::height / 2 - 10;
- output += Term::clear;
- empty_bg = fmt::format(
- "{banner}"
- "{mv1}{titleFg}{b}AltData Monitor"
- "{mv2}{hiFg}1 {mainFg}| Show NET box"
- "{mv3}{hiFg}2 {mainFg}| Show PROC box"
- "{mv4}{hiFg}q {mainFg}| Quit",
- "banner"_a = Draw::banner_gen(y, 0, true),
- "titleFg"_a = Theme::c("title"), "b"_a = Fx::b, "hiFg"_a = Theme::c("hi_fg"), "mainFg"_a = Theme::c("main_fg"),
- "mv1"_a = Mv::to(y+6, x),
- "mv2"_a = Mv::to(y+8, x),
- "mv3"_a = Mv::to(y+9, x),
- "mv4"_a = Mv::to(y+10, x)
- );
- }
- output += empty_bg;
- }
- //! DEBUG stats -->
- if (Global::debug and not Menu::active) {
- output += fmt::format("{pre}{box:5.5} {collect:>12.12} {draw:>12.12}{post}",
- "pre"_a = debug_bg + Theme::c("title") + Fx::b,
- "box"_a = "box", "collect"_a = "collect", "draw"_a = "draw",
- "post"_a = Theme::c("main_fg") + Fx::ub
- );
- static auto loc = std::locale(std::locale::classic(), new MyNumPunct);
- #ifdef GPU_SUPPORT
- for (const string name : {"cpu", "mem", "net", "proc", "gpu", "total"}) {
- #else
- for (const string name : {"cpu", "mem", "net", "proc", "total"}) {
- #endif
- if (not debug_times.contains(name)) debug_times[name] = {0,0};
- const auto& [time_collect, time_draw] = debug_times.at(name);
- if (name == "total") output += Fx::b;
- output += fmt::format(loc, "{mvLD}{name:5.5} {collect:12L} {draw:12L}",
- "mvLD"_a = Mv::l(31) + Mv::d(1),
- "name"_a = name,
- "collect"_a = time_collect,
- "draw"_a = time_draw
- );
- }
- }
- //? If overlay isn't empty, print output without color and then print overlay on top
- const bool term_sync = Config::getB("terminal_sync");
- cout << (term_sync ? Term::sync_start : "") << (conf.overlay.empty()
- ? output
- : (output.empty() ? "" : Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output)) + conf.overlay)
- << (term_sync ? Term::sync_end : "") << flush;
- }
- //* ----------------------------------------------- THREAD LOOP -----------------------------------------------
- return {};
- }
- //? ------------------------------------------ Secondary thread end -----------------------------------------------
- //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values
- void run(const string& box, bool no_update, bool force_redraw) {
- atomic_wait_for(active, true, 5000);
- if (active) {
- Logger::error("Stall in Runner thread, restarting!");
- active = false;
- // exit(1);
- pthread_cancel(Runner::runner_id);
- // Wait for the thread to actually terminate before creating a new one
- void* thread_result;
- int join_result = pthread_join(Runner::runner_id, &thread_result);
- if (join_result != 0) {
- Logger::warning("Failed to join cancelled thread: " + string(strerror(join_result)));
- }
- if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
- Global::exit_error_msg = "Failed to re-create _runner thread!";
- clean_quit(1);
- }
- }
- if (stopping or Global::resized) return;
- if (box == "overlay") {
- const bool term_sync = Config::getB("terminal_sync");
- cout << (term_sync ? Term::sync_start : "") << Global::overlay << (term_sync ? Term::sync_end : "") << flush;
- }
- else if (box == "clock") {
- const bool term_sync = Config::getB("terminal_sync");
- cout << (term_sync ? Term::sync_start : "") << Global::clock << (term_sync ? Term::sync_end : "") << flush;
- }
- else {
- Config::unlock();
- Config::lock();
- current_conf = {
- (box == "all" ? Config::current_boxes : vector{box}),
- no_update, force_redraw,
- (not Config::getB("tty_mode") and Config::getB("background_update")),
- Global::overlay,
- Global::clock
- };
- if (Menu::active and not current_conf.background_update) Global::overlay.clear();
- thread_trigger();
- atomic_wait_for(active, false, 10);
- }
- }
- //* Stops any work being done in runner thread and checks for thread errors
- void stop() {
- stopping = true;
- int ret = pthread_mutex_trylock(&mtx);
- if (ret != EBUSY and not Global::quitting) {
- if (active) active = false;
- Global::exit_error_msg = "Runner thread died unexpectedly!";
- clean_quit(1);
- }
- else if (ret == EBUSY) {
- atomic_wait_for(active, true, 5000);
- if (active) {
- active = false;
- if (Global::quitting) {
- return;
- }
- else {
- Global::exit_error_msg = "No response from Runner thread, quitting!";
- clean_quit(1);
- }
- }
- thread_trigger();
- atomic_wait_for(active, false, 100);
- atomic_wait_for(active, true, 100);
- }
- stopping = false;
- }
- }
- static auto configure_tty_mode(std::optional<bool> force_tty) {
- if (force_tty.has_value()) {
- Config::set("tty_mode", force_tty.value());
- Logger::debug("TTY mode set via command line");
- }
- #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
- else if (Term::current_tty.starts_with("/dev/tty")) {
- Config::set("tty_mode", true);
- Logger::debug("Auto detect real TTY");
- }
- #endif
- Logger::debug(fmt::format("TTY mode enabled: {}", Config::getB("tty_mode")));
- }
- //* --------------------------------------------- Main starts here! ---------------------------------------------------
- [[nodiscard]] auto btop_main(const std::span<const std::string_view> args) -> int {
- //? ------------------------------------------------ INIT ---------------------------------------------------------
- Global::start_time = time_s();
- //? Save real and effective userid's and drop privileges until needed if running with SUID bit set
- Global::real_uid = getuid();
- Global::set_uid = geteuid();
- if (Global::real_uid != Global::set_uid) {
- if (seteuid(Global::real_uid) != 0) {
- Global::real_uid = Global::set_uid;
- Global::exit_error_msg = "Failed to change effective user ID. Unset btop SUID bit to ensure security on this system. Quitting!";
- clean_quit(1);
- }
- }
- Cli::Cli cli;
- {
- // Get the cli options or return with an exit code
- auto result = Cli::parse(args);
- if (result.has_value()) {
- cli = result.value();
- } else {
- auto error = result.error();
- if (error != 0) {
- Cli::usage();
- Cli::help_hint();
- }
- return error;
- }
- }
- Global::debug = cli.debug;
- {
- const auto config_dir = Config::get_config_dir();
- if (config_dir.has_value()) {
- Config::conf_dir = config_dir.value();
- if (cli.config_file.has_value()) {
- Config::conf_file = cli.config_file.value();
- } else {
- Config::conf_file = Config::conf_dir / "btop.conf";
- }
- Logger::logfile = Config::get_log_file();
- Theme::user_theme_dir = Config::conf_dir / "themes";
- // If necessary create the user theme directory
- std::error_code error;
- if (not fs::exists(Theme::user_theme_dir, error) and not fs::create_directories(Theme::user_theme_dir, error)) {
- Theme::user_theme_dir.clear();
- Logger::warning("Failed to create user theme directory: " + error.message());
- }
- }
- }
- //? Try to find global btop theme path relative to binary path
- #ifdef __linux__
- { std::error_code ec;
- Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
- }
- #elif __APPLE__
- {
- char buf [PATH_MAX];
- uint32_t bufsize = PATH_MAX;
- if(!_NSGetExecutablePath(buf, &bufsize))
- Global::self_path = fs::path(buf).remove_filename();
- }
- #elif __NetBSD__
- {
- int mib[4];
- char buf[PATH_MAX];
- size_t bufsize = sizeof buf;
- mib[0] = CTL_KERN;
- mib[1] = KERN_PROC_ARGS;
- mib[2] = getpid();
- mib[3] = KERN_PROC_PATHNAME;
- if (sysctl(mib, 4, buf, &bufsize, NULL, 0) == 0)
- Global::self_path = fs::path(buf).remove_filename();
- }
- #endif
- if (std::error_code ec; not Global::self_path.empty()) {
- Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
- if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
- }
- //? If relative path failed, check two most common absolute paths
- if (Theme::theme_dir.empty()) {
- for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
- if (fs::is_directory(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
- Theme::theme_dir = fs::path(theme_path);
- break;
- }
- }
- }
- //? Set custom themes directory from command line if provided
- if (cli.themes_dir.has_value()) {
- Theme::custom_theme_dir = cli.themes_dir.value();
- Logger::info("Using custom themes directory: " + Theme::custom_theme_dir.string());
- }
- //? Config init
- init_config(cli.low_color, cli.filter);
- //? Try to find and set a UTF-8 locale
- if (std::setlocale(LC_ALL, "") != nullptr and not std::string_view { std::setlocale(LC_ALL, "") }.contains(";")
- and str_to_upper(s_replace((string)std::setlocale(LC_ALL, ""), "-", "")).ends_with("UTF8")) {
- Logger::debug("Using locale " + std::locale().name());
- }
- else {
- string found;
- bool set_failure{};
- for (const auto loc_env : array{"LANG", "LC_ALL", "LC_CTYPE"}) {
- if (std::getenv(loc_env) != nullptr and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) {
- found = std::getenv(loc_env);
- if (std::setlocale(LC_ALL, found.c_str()) == nullptr) {
- set_failure = true;
- Logger::warning("Failed to set locale " + found + " continuing anyway.");
- }
- }
- }
- if (found.empty()) {
- if (setenv("LC_ALL", "", 1) == 0 and setenv("LANG", "", 1) == 0) {
- try {
- if (const auto loc = std::locale("").name(); not loc.empty() and loc != "*") {
- for (auto& l : ssplit(loc, ';')) {
- if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) {
- found = l.substr(l.find('=') + 1);
- if (std::setlocale(LC_ALL, found.c_str()) != nullptr) {
- break;
- }
- }
- }
- }
- }
- catch (...) { found.clear(); }
- }
- }
- //
- #ifdef __APPLE__
- if (found.empty()) {
- CFLocaleRef cflocale = CFLocaleCopyCurrent();
- CFStringRef id_value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleIdentifier);
- auto loc_id = CFStringGetCStringPtr(id_value, kCFStringEncodingUTF8);
- CFRelease(cflocale);
- std::string cur_locale = (loc_id != nullptr ? loc_id : "");
- if (cur_locale.empty()) {
- Logger::warning("No UTF-8 locale detected! Some symbols might not display correctly.");
- }
- else if (std::setlocale(LC_ALL, string(cur_locale + ".UTF-8").c_str()) != nullptr) {
- Logger::debug("Setting LC_ALL=" + cur_locale + ".UTF-8");
- }
- else if(std::setlocale(LC_ALL, "en_US.UTF-8") != nullptr) {
- Logger::debug("Setting LC_ALL=en_US.UTF-8");
- }
- else {
- Logger::warning("Failed to set macos locale, continuing anyway.");
- }
- }
- #else
- if (found.empty() and cli.force_utf) {
- Logger::warning("No UTF-8 locale detected! Forcing start with --force-utf argument.");
- } else if (found.empty()) {
- Global::exit_error_msg = "No UTF-8 locale detected!\nUse --force-utf argument to force start if you're sure your terminal can handle it.";
- clean_quit(1);
- }
- #endif
- else if (not set_failure) {
- Logger::debug("Setting LC_ALL=" + found);
- }
- }
- //? Initialize terminal and set options
- if (not Term::init()) {
- Global::exit_error_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
- clean_quit(1);
- }
- if (Term::current_tty != "unknown") {
- Logger::info("Running on " + Term::current_tty);
- }
- configure_tty_mode(cli.force_tty);
- //? Check for valid terminal dimensions
- {
- int t_count = 0;
- while (Term::width <= 0 or Term::width > 10000 or Term::height <= 0 or Term::height > 10000) {
- sleep_ms(10);
- Term::refresh();
- if (++t_count == 100) {
- Global::exit_error_msg = "Failed to get size of terminal!";
- clean_quit(1);
- }
- }
- }
- //? Platform dependent init and error check
- try {
- Shared::init();
- }
- catch (const std::exception& e) {
- Global::exit_error_msg = "Exception in Shared::init() -> " + string{e.what()};
- clean_quit(1);
- }
- if (not Config::set_boxes(Config::getS("shown_boxes"))) {
- Config::set_boxes("cpu mem net proc");
- Config::set("shown_boxes", "cpu mem net proc"s);
- }
- //? Update list of available themes and generate the selected theme
- Theme::updateThemes();
- Theme::setTheme();
- //? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
- std::atexit(_exit_handler);
- std::signal(SIGINT, _signal_handler);
- std::signal(SIGTSTP, _signal_handler);
- std::signal(SIGCONT, _signal_handler);
- std::signal(SIGWINCH, _signal_handler);
- std::signal(SIGUSR1, _signal_handler);
- std::signal(SIGUSR2, _signal_handler);
- // Add crash handlers to restore terminal on crash
- std::signal(SIGSEGV, _crash_handler);
- std::signal(SIGABRT, _crash_handler);
- std::signal(SIGTRAP, _crash_handler);
- std::signal(SIGBUS, _crash_handler);
- std::signal(SIGILL, _crash_handler);
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, SIGUSR1);
- pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask);
- if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
- Global::exit_error_msg = "Failed to create _runner thread!";
- clean_quit(1);
- }
- else {
- Global::_runner_started = true;
- }
- //? Calculate sizes of all boxes
- Config::presetsValid(Config::getS("presets"));
- if (cli.preset.has_value()) {
- Config::current_preset = min(static_cast<std::int32_t>(cli.preset.value()), static_cast<std::int32_t>(Config::preset_list.size() - 1));
- Config::apply_preset(Config::preset_list.at(Config::current_preset));
- }
- {
- const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes"));
- if (Term::height < y or Term::width < x) {
- pthread_sigmask(SIG_SETMASK, &Input::signal_mask, &mask);
- term_resize(true);
- pthread_sigmask(SIG_SETMASK, &mask, nullptr);
- Global::resized = false;
- }
- }
- Draw::calcSizes();
- //? Print out box outlines
- const bool term_sync = Config::getB("terminal_sync");
- cout << (term_sync ? Term::sync_start : "") << Cpu::box << Mem::box << Net::box << Proc::box << (term_sync ? Term::sync_end : "") << flush;
- //? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
- if (cli.updates.has_value()) {
- Config::set("update_ms", static_cast<int>(cli.updates.value()));
- }
- uint64_t update_ms = Config::getI("update_ms");
- auto future_time = time_ms();
- try {
- while (not true not_eq not false) {
- //? Check for exceptions in secondary thread and exit with fail signal if true
- if (Global::thread_exception) {
- clean_quit(1);
- }
- else if (Global::should_quit) {
- clean_quit(0);
- }
- else if (Global::should_sleep) {
- Global::should_sleep = false;
- _sleep();
- }
- //? Hot reload config from CTRL + R or SIGUSR2
- else if (Global::reload_conf) {
- Global::reload_conf = false;
- if (Runner::active) Runner::stop();
- Config::unlock();
- init_config(cli.low_color, cli.filter);
- Theme::updateThemes();
- Theme::setTheme();
- Draw::banner_gen(0, 0, false, true);
- Global::resized = true;
- }
- //? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
- term_resize(Global::resized);
- //? Trigger secondary thread to redraw if terminal has been resized
- if (Global::resized) {
- Draw::calcSizes();
- Draw::update_clock(true);
- Global::resized = false;
- if (Menu::active) Menu::process();
- else Runner::run("all", true, true);
- atomic_wait_for(Runner::active, true, 1000);
- }
- //? Update clock if needed
- if (Draw::update_clock() and not Menu::active) {
- Runner::run("clock");
- }
- //? Start secondary collect & draw thread at the interval set by <update_ms> config value
- if (time_ms() >= future_time and not Global::resized) {
- Runner::run("all");
- update_ms = Config::getI("update_ms");
- future_time = time_ms() + update_ms;
- }
- //? Loop over input polling and input action processing
- for (auto current_time = time_ms(); current_time < future_time; current_time = time_ms()) {
- //? Check for external clock changes and for changes to the update timer
- if (std::cmp_not_equal(update_ms, Config::getI("update_ms"))) {
- update_ms = Config::getI("update_ms");
- future_time = time_ms() + update_ms;
- }
- else if (future_time - current_time > update_ms) {
- future_time = current_time;
- }
- //? Poll for input and process any input detected
- else if (Input::poll(min((uint64_t)1000, future_time - current_time))) {
- if (not Runner::active) Config::unlock();
- if (Menu::active) Menu::process(Input::get());
- else Input::process(Input::get());
- }
- //? Break the loop at 1000ms intervals or if input polling was interrupted
- else break;
- }
- }
- }
- catch (const std::exception& e) {
- Global::exit_error_msg = "Exception in main loop -> " + string{e.what()};
- clean_quit(1);
- }
- return 0;
- }
|