btop.cpp 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /* Copyright 2021 Aristocratos (jakob@qvantnet.com)
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. indent = tab
  12. tab-size = 4
  13. */
  14. #include "btop.hpp"
  15. #include <algorithm>
  16. #include <csignal>
  17. #include <clocale>
  18. #include <filesystem>
  19. #include <iterator>
  20. #include <optional>
  21. #include <pthread.h>
  22. #include <span>
  23. #include <string_view>
  24. #ifdef __FreeBSD__
  25. #include <pthread_np.h>
  26. #endif
  27. #include <thread>
  28. #include <numeric>
  29. #include <ranges>
  30. #include <unistd.h>
  31. #include <cmath>
  32. #include <iostream>
  33. #include <exception>
  34. #include <tuple>
  35. #include <regex>
  36. #include <chrono>
  37. #include <utility>
  38. #include <semaphore>
  39. #ifdef __APPLE__
  40. #include <CoreFoundation/CoreFoundation.h>
  41. #include <mach-o/dyld.h>
  42. #include <limits.h>
  43. #endif
  44. #ifdef __NetBSD__
  45. #include <sys/param.h>
  46. #include <sys/sysctl.h>
  47. #include <unistd.h>
  48. #endif
  49. #include "btop_cli.hpp"
  50. #include "btop_shared.hpp"
  51. #include "btop_tools.hpp"
  52. #include "btop_config.hpp"
  53. #include "btop_input.hpp"
  54. #include "btop_theme.hpp"
  55. #include "btop_draw.hpp"
  56. #include "btop_menu.hpp"
  57. #include "fmt/core.h"
  58. #include "fmt/ostream.h"
  59. using std::atomic;
  60. using std::cout;
  61. using std::flush;
  62. using std::min;
  63. using std::string;
  64. using std::string_view;
  65. using std::to_string;
  66. using std::vector;
  67. namespace fs = std::filesystem;
  68. using namespace Tools;
  69. using namespace std::chrono_literals;
  70. using namespace std::literals;
  71. namespace Global {
  72. const vector<array<string, 2>> Banner_src = {
  73. {"#E62525", "██████╗ ████████╗ ██████╗ ██████╗"},
  74. {"#CD2121", "██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗ ██╗ ██╗"},
  75. {"#B31D1D", "██████╔╝ ██║ ██║ ██║██████╔╝ ██████╗██████╗"},
  76. {"#9A1919", "██╔══██╗ ██║ ██║ ██║██╔═══╝ ╚═██╔═╝╚═██╔═╝"},
  77. {"#801414", "██████╔╝ ██║ ╚██████╔╝██║ ╚═╝ ╚═╝"},
  78. {"#000000", "╚═════╝ ╚═╝ ╚═════╝ ╚═╝"},
  79. };
  80. const string Version = "1.4.5";
  81. int coreCount;
  82. string overlay;
  83. string clock;
  84. string bg_black = "\x1b[0;40m";
  85. string fg_white = "\x1b[1;97m";
  86. string fg_green = "\x1b[1;92m";
  87. string fg_red = "\x1b[0;91m";
  88. uid_t real_uid, set_uid;
  89. fs::path self_path;
  90. string exit_error_msg;
  91. atomic<bool> thread_exception (false);
  92. bool debug{};
  93. uint64_t start_time;
  94. atomic<bool> resized (false);
  95. atomic<bool> quitting (false);
  96. atomic<bool> should_quit (false);
  97. atomic<bool> should_sleep (false);
  98. atomic<bool> _runner_started (false);
  99. atomic<bool> init_conf (false);
  100. atomic<bool> reload_conf (false);
  101. }
  102. //* Handler for SIGWINCH and general resizing events, does nothing if terminal hasn't been resized unless force=true
  103. void term_resize(bool force) {
  104. static atomic<bool> resizing (false);
  105. if (Input::polling) {
  106. Global::resized = true;
  107. Input::interrupt();
  108. return;
  109. }
  110. atomic_lock lck(resizing, true);
  111. if (auto refreshed = Term::refresh(true); refreshed or force) {
  112. if (force and refreshed) force = false;
  113. }
  114. else return;
  115. #ifdef GPU_SUPPORT
  116. static const array<string, 10> all_boxes = {"gpu5", "cpu", "mem", "net", "proc", "gpu0", "gpu1", "gpu2", "gpu3", "gpu4"};
  117. #else
  118. static const array<string, 5> all_boxes = {"", "cpu", "mem", "net", "proc"};
  119. #endif
  120. Global::resized = true;
  121. if (Runner::active) Runner::stop();
  122. Term::refresh();
  123. Config::unlock();
  124. auto boxes = Config::getS("shown_boxes");
  125. auto min_size = Term::get_min_size(boxes);
  126. auto minWidth = min_size.at(0), minHeight = min_size.at(1);
  127. while (not force or (Term::width < minWidth or Term::height < minHeight)) {
  128. sleep_ms(100);
  129. if (Term::width < minWidth or Term::height < minHeight) {
  130. int width = Term::width, height = Term::height;
  131. cout << fmt::format("{clear}{bg_black}{fg_white}"
  132. "{mv1}Terminal size too small:"
  133. "{mv2} Width = {fg_width}{width} {fg_white}Height = {fg_height}{height}"
  134. "{mv3}{fg_white}Needed for current config:"
  135. "{mv4}Width = {minWidth} Height = {minHeight}",
  136. "clear"_a = Term::clear, "bg_black"_a = Global::bg_black, "fg_white"_a = Global::fg_white,
  137. "mv1"_a = Mv::to((height / 2) - 2, (width / 2) - 11),
  138. "mv2"_a = Mv::to((height / 2) - 1, (width / 2) - 10),
  139. "fg_width"_a = (width < minWidth ? Global::fg_red : Global::fg_green),
  140. "width"_a = width,
  141. "fg_height"_a = (height < minHeight ? Global::fg_red : Global::fg_green),
  142. "height"_a = height,
  143. "mv3"_a = Mv::to((height / 2) + 1, (width / 2) - 12),
  144. "mv4"_a = Mv::to((height / 2) + 2, (width / 2) - 10),
  145. "minWidth"_a = minWidth,
  146. "minHeight"_a = minHeight
  147. ) << std::flush;
  148. bool got_key = false;
  149. for (; not Term::refresh() and not got_key; got_key = Input::poll(10));
  150. if (got_key) {
  151. auto key = Input::get();
  152. if (key == "q")
  153. clean_quit(0);
  154. else if (key.size() == 1 and isint(key)) {
  155. auto intKey = stoi(key);
  156. #ifdef GPU_SUPPORT
  157. if ((intKey == 0 and Gpu::count >= 5) or (intKey >= 5 and intKey - 4 <= Gpu::count)) {
  158. #else
  159. if (intKey > 0 and intKey < 5) {
  160. #endif
  161. const auto& box = all_boxes.at(intKey);
  162. Config::current_preset = -1;
  163. Config::toggle_box(box);
  164. boxes = Config::getS("shown_boxes");
  165. }
  166. }
  167. }
  168. min_size = Term::get_min_size(boxes);
  169. minWidth = min_size.at(0);
  170. minHeight = min_size.at(1);
  171. }
  172. else if (not Term::refresh()) break;
  173. }
  174. Input::interrupt();
  175. }
  176. //* Exit handler; stops threads, restores terminal and saves config changes
  177. void clean_quit(int sig) {
  178. if (Global::quitting) return;
  179. Global::quitting = true;
  180. Runner::stop();
  181. if (Global::_runner_started) {
  182. #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__
  183. if (pthread_join(Runner::runner_id, nullptr) != 0) {
  184. Logger::warning("Failed to join _runner thread on exit!");
  185. pthread_cancel(Runner::runner_id);
  186. }
  187. #else
  188. constexpr struct timespec ts { .tv_sec = 5, .tv_nsec = 0 };
  189. if (pthread_timedjoin_np(Runner::runner_id, nullptr, &ts) != 0) {
  190. Logger::warning("Failed to join _runner thread on exit!");
  191. pthread_cancel(Runner::runner_id);
  192. }
  193. #endif
  194. }
  195. #ifdef GPU_SUPPORT
  196. Gpu::Nvml::shutdown();
  197. Gpu::Rsmi::shutdown();
  198. #endif
  199. Config::write();
  200. if (Term::initialized) {
  201. Input::clear();
  202. Term::restore();
  203. }
  204. if (not Global::exit_error_msg.empty()) {
  205. sig = 1;
  206. Logger::error(Global::exit_error_msg);
  207. fmt::println(std::cerr, "{}ERROR: {}{}{}", Global::fg_red, Global::fg_white, Global::exit_error_msg, Fx::reset);
  208. }
  209. Logger::info("Quitting! Runtime: " + sec_to_dhms(time_s() - Global::start_time));
  210. const auto excode = (sig != -1 ? sig : 0);
  211. #if defined __APPLE__ || defined __OpenBSD__ || defined __NetBSD__
  212. _Exit(excode);
  213. #else
  214. quick_exit(excode);
  215. #endif
  216. }
  217. //* Handler for SIGTSTP; stops threads, restores terminal and sends SIGSTOP
  218. static void _sleep() {
  219. Runner::stop();
  220. Term::restore();
  221. std::raise(SIGSTOP);
  222. }
  223. //* Handler for SIGCONT; re-initialize terminal and force a resize event
  224. static void _resume() {
  225. Term::init();
  226. term_resize(true);
  227. }
  228. static void _exit_handler() {
  229. clean_quit(-1);
  230. }
  231. static void _crash_handler(const int sig) {
  232. // Restore terminal before crashing
  233. if (Term::initialized) {
  234. Term::restore();
  235. }
  236. // Re-raise the signal to get default behavior (core dump)
  237. std::signal(sig, SIG_DFL);
  238. std::raise(sig);
  239. }
  240. static void _signal_handler(const int sig) {
  241. switch (sig) {
  242. case SIGINT:
  243. if (Runner::active) {
  244. Global::should_quit = true;
  245. Runner::stopping = true;
  246. Input::interrupt();
  247. }
  248. else {
  249. clean_quit(0);
  250. }
  251. break;
  252. case SIGTSTP:
  253. if (Runner::active) {
  254. Global::should_sleep = true;
  255. Runner::stopping = true;
  256. Input::interrupt();
  257. }
  258. else {
  259. _sleep();
  260. }
  261. break;
  262. case SIGCONT:
  263. _resume();
  264. break;
  265. case SIGWINCH:
  266. term_resize();
  267. break;
  268. case SIGUSR1:
  269. // Input::poll interrupt
  270. break;
  271. case SIGUSR2:
  272. Global::reload_conf = true;
  273. Input::interrupt();
  274. break;
  275. }
  276. }
  277. //* Config init
  278. void init_config(bool low_color, std::optional<std::string>& filter) {
  279. atomic_lock lck(Global::init_conf);
  280. vector<string> load_warnings;
  281. Config::load(Config::conf_file, load_warnings);
  282. Config::set("lowcolor", (low_color ? true : not Config::getB("truecolor")));
  283. static bool first_init = true;
  284. if (Global::debug and first_init) {
  285. Logger::set("DEBUG");
  286. Logger::debug("Running in DEBUG mode!");
  287. }
  288. else Logger::set(Config::getS("log_level"));
  289. if (filter.has_value()) {
  290. Config::set("proc_filter", filter.value());
  291. }
  292. static string log_level;
  293. if (const string current_level = Config::getS("log_level"); log_level != current_level) {
  294. log_level = current_level;
  295. Logger::info("Logger set to " + (Global::debug ? "DEBUG" : log_level));
  296. }
  297. for (const auto& err_str : load_warnings) Logger::warning(err_str);
  298. first_init = false;
  299. }
  300. //* Manages secondary thread for collection and drawing of boxes
  301. namespace Runner {
  302. atomic<bool> active (false);
  303. atomic<bool> stopping (false);
  304. atomic<bool> waiting (false);
  305. atomic<bool> redraw (false);
  306. atomic<bool> coreNum_reset (false);
  307. //* Setup semaphore for triggering thread to do work
  308. // TODO: This can be made a local without too much effort.
  309. std::binary_semaphore do_work { 0 };
  310. inline void thread_wait() { do_work.acquire(); }
  311. inline void thread_trigger() { do_work.release(); }
  312. //* RAII wrapper for pthread_mutex locking
  313. class thread_lock {
  314. pthread_mutex_t& pt_mutex;
  315. public:
  316. int status;
  317. explicit thread_lock(pthread_mutex_t& mtx) : pt_mutex(mtx) {
  318. pthread_mutex_init(&pt_mutex, nullptr);
  319. status = pthread_mutex_lock(&pt_mutex);
  320. }
  321. ~thread_lock() noexcept {
  322. if (status == 0)
  323. pthread_mutex_unlock(&pt_mutex);
  324. }
  325. thread_lock(const thread_lock& other) = delete;
  326. thread_lock& operator=(const thread_lock& other) = delete;
  327. thread_lock(thread_lock&& other) = delete;
  328. thread_lock& operator=(thread_lock&& other) = delete;
  329. };
  330. //* Wrapper for raising privileges when using SUID bit
  331. class gain_priv {
  332. int status = -1;
  333. public:
  334. gain_priv() {
  335. if (Global::real_uid != Global::set_uid)
  336. this->status = seteuid(Global::set_uid);
  337. }
  338. ~gain_priv() noexcept {
  339. if (status == 0)
  340. status = seteuid(Global::real_uid);
  341. }
  342. gain_priv(const gain_priv& other) = delete;
  343. gain_priv& operator=(const gain_priv& other) = delete;
  344. gain_priv(gain_priv&& other) = delete;
  345. gain_priv& operator=(gain_priv&& other) = delete;
  346. };
  347. string output;
  348. string empty_bg;
  349. bool pause_output{};
  350. sigset_t mask;
  351. pthread_t runner_id;
  352. pthread_mutex_t mtx;
  353. enum debug_actions {
  354. collect_begin,
  355. collect_done,
  356. draw_begin,
  357. draw_begin_only,
  358. draw_done
  359. };
  360. enum debug_array {
  361. collect,
  362. draw
  363. };
  364. string debug_bg;
  365. std::unordered_map<string, array<uint64_t, 2>> debug_times;
  366. class MyNumPunct : public std::numpunct<char>
  367. {
  368. protected:
  369. virtual char do_thousands_sep() const override { return '\''; }
  370. virtual std::string do_grouping() const override { return "\03"; }
  371. };
  372. struct runner_conf {
  373. vector<string> boxes;
  374. bool no_update;
  375. bool force_redraw;
  376. bool background_update;
  377. string overlay;
  378. string clock;
  379. };
  380. struct runner_conf current_conf;
  381. static void debug_timer(const char* name, const int action) {
  382. switch (action) {
  383. case collect_begin:
  384. debug_times[name].at(collect) = time_micros();
  385. return;
  386. case collect_done:
  387. debug_times[name].at(collect) = time_micros() - debug_times[name].at(collect);
  388. debug_times["total"].at(collect) += debug_times[name].at(collect);
  389. return;
  390. case draw_begin_only:
  391. debug_times[name].at(draw) = time_micros();
  392. return;
  393. case draw_begin:
  394. debug_times[name].at(draw) = time_micros();
  395. debug_times[name].at(collect) = debug_times[name].at(draw) - debug_times[name].at(collect);
  396. debug_times["total"].at(collect) += debug_times[name].at(collect);
  397. return;
  398. case draw_done:
  399. debug_times[name].at(draw) = time_micros() - debug_times[name].at(draw);
  400. debug_times["total"].at(draw) += debug_times[name].at(draw);
  401. return;
  402. }
  403. }
  404. //? ------------------------------- Secondary thread: async launcher and drawing ----------------------------------
  405. static void * _runner(void *) {
  406. //? Block some signals in this thread to avoid deadlock from any signal handlers trying to stop this thread
  407. sigemptyset(&mask);
  408. // sigaddset(&mask, SIGINT);
  409. // sigaddset(&mask, SIGTSTP);
  410. sigaddset(&mask, SIGWINCH);
  411. sigaddset(&mask, SIGTERM);
  412. pthread_sigmask(SIG_BLOCK, &mask, nullptr);
  413. //? pthread_mutex_lock to lock thread and monitor health from main thread
  414. thread_lock pt_lck(mtx);
  415. if (pt_lck.status != 0) {
  416. Global::exit_error_msg = "Exception in runner thread -> pthread_mutex_lock error id: " + to_string(pt_lck.status);
  417. Global::thread_exception = true;
  418. Input::interrupt();
  419. stopping = true;
  420. }
  421. //* ----------------------------------------------- THREAD LOOP -----------------------------------------------
  422. while (not Global::quitting) {
  423. thread_wait();
  424. atomic_wait_for(active, true, 5000);
  425. if (active) {
  426. Global::exit_error_msg = "Runner thread failed to get active lock!";
  427. Global::thread_exception = true;
  428. Input::interrupt();
  429. stopping = true;
  430. }
  431. if (stopping or Global::resized) {
  432. sleep_ms(1);
  433. continue;
  434. }
  435. //? Atomic lock used for blocking non thread-safe actions in main thread
  436. atomic_lock lck(active);
  437. //? Set effective user if SUID bit is set
  438. gain_priv powers{};
  439. auto& conf = current_conf;
  440. //! DEBUG stats
  441. if (Global::debug) {
  442. if (debug_bg.empty() or redraw)
  443. Runner::debug_bg = Draw::createBox(2, 2, 33,
  444. #ifdef GPU_SUPPORT
  445. 9,
  446. #else
  447. 8,
  448. #endif
  449. "", true, "μs");
  450. debug_times.clear();
  451. debug_times["total"] = {0, 0};
  452. }
  453. output.clear();
  454. //* Run collection and draw functions for all boxes
  455. try {
  456. #ifdef GPU_SUPPORT
  457. //? GPU data collection
  458. const bool gpu_in_cpu_panel = Gpu::gpu_names.size() > 0 and (
  459. Config::getS("cpu_graph_lower").starts_with("gpu-")
  460. or (Config::getS("cpu_graph_lower") == "Auto")
  461. or Config::getS("cpu_graph_upper").starts_with("gpu-")
  462. or (Gpu::shown == 0 and Config::getS("show_gpu_info") != "Off")
  463. );
  464. vector<unsigned int> gpu_panels = {};
  465. for (auto& box : conf.boxes)
  466. if (box.starts_with("gpu"))
  467. gpu_panels.push_back(box.back()-'0');
  468. vector<Gpu::gpu_info> gpus;
  469. if (gpu_in_cpu_panel or not gpu_panels.empty()) {
  470. if (Global::debug) debug_timer("gpu", collect_begin);
  471. gpus = Gpu::collect(conf.no_update);
  472. if (Global::debug) debug_timer("gpu", collect_done);
  473. }
  474. auto& gpus_ref = gpus;
  475. #else
  476. vector<Gpu::gpu_info> gpus_ref{};
  477. #endif
  478. //? CPU
  479. if (v_contains(conf.boxes, "cpu")) {
  480. try {
  481. if (Global::debug) debug_timer("cpu", collect_begin);
  482. //? Start collect
  483. auto cpu = Cpu::collect(conf.no_update);
  484. if (coreNum_reset) {
  485. coreNum_reset = false;
  486. Cpu::core_mapping = Cpu::get_core_mapping();
  487. Global::resized = true;
  488. Input::interrupt();
  489. continue;
  490. }
  491. if (Global::debug) debug_timer("cpu", draw_begin);
  492. //? Draw box
  493. if (not pause_output) output += Cpu::draw(cpu, gpus_ref, conf.force_redraw, conf.no_update);
  494. if (Global::debug) debug_timer("cpu", draw_done);
  495. }
  496. catch (const std::exception& e) {
  497. throw std::runtime_error("Cpu:: -> " + string{e.what()});
  498. }
  499. }
  500. #ifdef GPU_SUPPORT
  501. //? GPU
  502. if (not gpu_panels.empty() and not gpus_ref.empty()) {
  503. try {
  504. if (Global::debug) debug_timer("gpu", draw_begin_only);
  505. //? Draw box
  506. if (not pause_output)
  507. for (unsigned long i = 0; i < gpu_panels.size(); ++i)
  508. output += Gpu::draw(gpus_ref[gpu_panels[i]], i, conf.force_redraw, conf.no_update);
  509. if (Global::debug) debug_timer("gpu", draw_done);
  510. }
  511. catch (const std::exception& e) {
  512. throw std::runtime_error("Gpu:: -> " + string{e.what()});
  513. }
  514. }
  515. #endif
  516. //? MEM
  517. if (v_contains(conf.boxes, "mem")) {
  518. try {
  519. if (Global::debug) debug_timer("mem", collect_begin);
  520. //? Start collect
  521. auto mem = Mem::collect(conf.no_update);
  522. if (Global::debug) debug_timer("mem", draw_begin);
  523. //? Draw box
  524. if (not pause_output) output += Mem::draw(mem, conf.force_redraw, conf.no_update);
  525. if (Global::debug) debug_timer("mem", draw_done);
  526. }
  527. catch (const std::exception& e) {
  528. throw std::runtime_error("Mem:: -> " + string{e.what()});
  529. }
  530. }
  531. //? NET
  532. if (v_contains(conf.boxes, "net")) {
  533. try {
  534. if (Global::debug) debug_timer("net", collect_begin);
  535. //? Start collect
  536. auto net = Net::collect(conf.no_update);
  537. if (Global::debug) debug_timer("net", draw_begin);
  538. //? Draw box
  539. if (not pause_output) output += Net::draw(net, conf.force_redraw, conf.no_update);
  540. if (Global::debug) debug_timer("net", draw_done);
  541. }
  542. catch (const std::exception& e) {
  543. throw std::runtime_error("Net:: -> " + string{e.what()});
  544. }
  545. }
  546. //? PROC
  547. if (v_contains(conf.boxes, "proc")) {
  548. try {
  549. if (Global::debug) debug_timer("proc", collect_begin);
  550. //? Start collect
  551. auto proc = Proc::collect(conf.no_update);
  552. if (Global::debug) debug_timer("proc", draw_begin);
  553. //? Draw box
  554. if (not pause_output) output += Proc::draw(proc, conf.force_redraw, conf.no_update);
  555. if (Global::debug) debug_timer("proc", draw_done);
  556. }
  557. catch (const std::exception& e) {
  558. throw std::runtime_error("Proc:: -> " + string{e.what()});
  559. }
  560. }
  561. }
  562. catch (const std::exception& e) {
  563. Global::exit_error_msg = "Exception in runner thread -> " + string{e.what()};
  564. Global::thread_exception = true;
  565. Input::interrupt();
  566. stopping = true;
  567. }
  568. if (stopping) {
  569. continue;
  570. }
  571. if (redraw or conf.force_redraw) {
  572. empty_bg.clear();
  573. redraw = false;
  574. }
  575. if (not pause_output) output += conf.clock;
  576. if (not conf.overlay.empty() and not conf.background_update) pause_output = true;
  577. if (output.empty() and not pause_output) {
  578. if (empty_bg.empty()) {
  579. const int x = Term::width / 2 - 10, y = Term::height / 2 - 10;
  580. output += Term::clear;
  581. empty_bg = fmt::format(
  582. "{banner}"
  583. "{mv1}{titleFg}{b}AltData Monitor"
  584. "{mv2}{hiFg}1 {mainFg}| Show NET box"
  585. "{mv3}{hiFg}2 {mainFg}| Show PROC box"
  586. "{mv4}{hiFg}q {mainFg}| Quit",
  587. "banner"_a = Draw::banner_gen(y, 0, true),
  588. "titleFg"_a = Theme::c("title"), "b"_a = Fx::b, "hiFg"_a = Theme::c("hi_fg"), "mainFg"_a = Theme::c("main_fg"),
  589. "mv1"_a = Mv::to(y+6, x),
  590. "mv2"_a = Mv::to(y+8, x),
  591. "mv3"_a = Mv::to(y+9, x),
  592. "mv4"_a = Mv::to(y+10, x)
  593. );
  594. }
  595. output += empty_bg;
  596. }
  597. //! DEBUG stats -->
  598. if (Global::debug and not Menu::active) {
  599. output += fmt::format("{pre}{box:5.5} {collect:>12.12} {draw:>12.12}{post}",
  600. "pre"_a = debug_bg + Theme::c("title") + Fx::b,
  601. "box"_a = "box", "collect"_a = "collect", "draw"_a = "draw",
  602. "post"_a = Theme::c("main_fg") + Fx::ub
  603. );
  604. static auto loc = std::locale(std::locale::classic(), new MyNumPunct);
  605. #ifdef GPU_SUPPORT
  606. for (const string name : {"cpu", "mem", "net", "proc", "gpu", "total"}) {
  607. #else
  608. for (const string name : {"cpu", "mem", "net", "proc", "total"}) {
  609. #endif
  610. if (not debug_times.contains(name)) debug_times[name] = {0,0};
  611. const auto& [time_collect, time_draw] = debug_times.at(name);
  612. if (name == "total") output += Fx::b;
  613. output += fmt::format(loc, "{mvLD}{name:5.5} {collect:12L} {draw:12L}",
  614. "mvLD"_a = Mv::l(31) + Mv::d(1),
  615. "name"_a = name,
  616. "collect"_a = time_collect,
  617. "draw"_a = time_draw
  618. );
  619. }
  620. }
  621. //? If overlay isn't empty, print output without color and then print overlay on top
  622. const bool term_sync = Config::getB("terminal_sync");
  623. cout << (term_sync ? Term::sync_start : "") << (conf.overlay.empty()
  624. ? output
  625. : (output.empty() ? "" : Fx::ub + Theme::c("inactive_fg") + Fx::uncolor(output)) + conf.overlay)
  626. << (term_sync ? Term::sync_end : "") << flush;
  627. }
  628. //* ----------------------------------------------- THREAD LOOP -----------------------------------------------
  629. return {};
  630. }
  631. //? ------------------------------------------ Secondary thread end -----------------------------------------------
  632. //* Runs collect and draw in a secondary thread, unlocks and locks config to update cached values
  633. void run(const string& box, bool no_update, bool force_redraw) {
  634. atomic_wait_for(active, true, 5000);
  635. if (active) {
  636. Logger::error("Stall in Runner thread, restarting!");
  637. active = false;
  638. // exit(1);
  639. pthread_cancel(Runner::runner_id);
  640. // Wait for the thread to actually terminate before creating a new one
  641. void* thread_result;
  642. int join_result = pthread_join(Runner::runner_id, &thread_result);
  643. if (join_result != 0) {
  644. Logger::warning("Failed to join cancelled thread: " + string(strerror(join_result)));
  645. }
  646. if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
  647. Global::exit_error_msg = "Failed to re-create _runner thread!";
  648. clean_quit(1);
  649. }
  650. }
  651. if (stopping or Global::resized) return;
  652. if (box == "overlay") {
  653. const bool term_sync = Config::getB("terminal_sync");
  654. cout << (term_sync ? Term::sync_start : "") << Global::overlay << (term_sync ? Term::sync_end : "") << flush;
  655. }
  656. else if (box == "clock") {
  657. const bool term_sync = Config::getB("terminal_sync");
  658. cout << (term_sync ? Term::sync_start : "") << Global::clock << (term_sync ? Term::sync_end : "") << flush;
  659. }
  660. else {
  661. Config::unlock();
  662. Config::lock();
  663. current_conf = {
  664. (box == "all" ? Config::current_boxes : vector{box}),
  665. no_update, force_redraw,
  666. (not Config::getB("tty_mode") and Config::getB("background_update")),
  667. Global::overlay,
  668. Global::clock
  669. };
  670. if (Menu::active and not current_conf.background_update) Global::overlay.clear();
  671. thread_trigger();
  672. atomic_wait_for(active, false, 10);
  673. }
  674. }
  675. //* Stops any work being done in runner thread and checks for thread errors
  676. void stop() {
  677. stopping = true;
  678. int ret = pthread_mutex_trylock(&mtx);
  679. if (ret != EBUSY and not Global::quitting) {
  680. if (active) active = false;
  681. Global::exit_error_msg = "Runner thread died unexpectedly!";
  682. clean_quit(1);
  683. }
  684. else if (ret == EBUSY) {
  685. atomic_wait_for(active, true, 5000);
  686. if (active) {
  687. active = false;
  688. if (Global::quitting) {
  689. return;
  690. }
  691. else {
  692. Global::exit_error_msg = "No response from Runner thread, quitting!";
  693. clean_quit(1);
  694. }
  695. }
  696. thread_trigger();
  697. atomic_wait_for(active, false, 100);
  698. atomic_wait_for(active, true, 100);
  699. }
  700. stopping = false;
  701. }
  702. }
  703. static auto configure_tty_mode(std::optional<bool> force_tty) {
  704. if (force_tty.has_value()) {
  705. Config::set("tty_mode", force_tty.value());
  706. Logger::debug("TTY mode set via command line");
  707. }
  708. #if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined(__NetBSD__)
  709. else if (Term::current_tty.starts_with("/dev/tty")) {
  710. Config::set("tty_mode", true);
  711. Logger::debug("Auto detect real TTY");
  712. }
  713. #endif
  714. Logger::debug(fmt::format("TTY mode enabled: {}", Config::getB("tty_mode")));
  715. }
  716. //* --------------------------------------------- Main starts here! ---------------------------------------------------
  717. [[nodiscard]] auto btop_main(const std::span<const std::string_view> args) -> int {
  718. //? ------------------------------------------------ INIT ---------------------------------------------------------
  719. Global::start_time = time_s();
  720. //? Save real and effective userid's and drop privileges until needed if running with SUID bit set
  721. Global::real_uid = getuid();
  722. Global::set_uid = geteuid();
  723. if (Global::real_uid != Global::set_uid) {
  724. if (seteuid(Global::real_uid) != 0) {
  725. Global::real_uid = Global::set_uid;
  726. Global::exit_error_msg = "Failed to change effective user ID. Unset btop SUID bit to ensure security on this system. Quitting!";
  727. clean_quit(1);
  728. }
  729. }
  730. Cli::Cli cli;
  731. {
  732. // Get the cli options or return with an exit code
  733. auto result = Cli::parse(args);
  734. if (result.has_value()) {
  735. cli = result.value();
  736. } else {
  737. auto error = result.error();
  738. if (error != 0) {
  739. Cli::usage();
  740. Cli::help_hint();
  741. }
  742. return error;
  743. }
  744. }
  745. Global::debug = cli.debug;
  746. {
  747. const auto config_dir = Config::get_config_dir();
  748. if (config_dir.has_value()) {
  749. Config::conf_dir = config_dir.value();
  750. if (cli.config_file.has_value()) {
  751. Config::conf_file = cli.config_file.value();
  752. } else {
  753. Config::conf_file = Config::conf_dir / "btop.conf";
  754. }
  755. Logger::logfile = Config::get_log_file();
  756. Theme::user_theme_dir = Config::conf_dir / "themes";
  757. // If necessary create the user theme directory
  758. std::error_code error;
  759. if (not fs::exists(Theme::user_theme_dir, error) and not fs::create_directories(Theme::user_theme_dir, error)) {
  760. Theme::user_theme_dir.clear();
  761. Logger::warning("Failed to create user theme directory: " + error.message());
  762. }
  763. }
  764. }
  765. //? Try to find global btop theme path relative to binary path
  766. #ifdef __linux__
  767. { std::error_code ec;
  768. Global::self_path = fs::read_symlink("/proc/self/exe", ec).remove_filename();
  769. }
  770. #elif __APPLE__
  771. {
  772. char buf [PATH_MAX];
  773. uint32_t bufsize = PATH_MAX;
  774. if(!_NSGetExecutablePath(buf, &bufsize))
  775. Global::self_path = fs::path(buf).remove_filename();
  776. }
  777. #elif __NetBSD__
  778. {
  779. int mib[4];
  780. char buf[PATH_MAX];
  781. size_t bufsize = sizeof buf;
  782. mib[0] = CTL_KERN;
  783. mib[1] = KERN_PROC_ARGS;
  784. mib[2] = getpid();
  785. mib[3] = KERN_PROC_PATHNAME;
  786. if (sysctl(mib, 4, buf, &bufsize, NULL, 0) == 0)
  787. Global::self_path = fs::path(buf).remove_filename();
  788. }
  789. #endif
  790. if (std::error_code ec; not Global::self_path.empty()) {
  791. Theme::theme_dir = fs::canonical(Global::self_path / "../share/btop/themes", ec);
  792. if (ec or not fs::is_directory(Theme::theme_dir) or access(Theme::theme_dir.c_str(), R_OK) == -1) Theme::theme_dir.clear();
  793. }
  794. //? If relative path failed, check two most common absolute paths
  795. if (Theme::theme_dir.empty()) {
  796. for (auto theme_path : {"/usr/local/share/btop/themes", "/usr/share/btop/themes"}) {
  797. if (fs::is_directory(fs::path(theme_path)) and access(theme_path, R_OK) != -1) {
  798. Theme::theme_dir = fs::path(theme_path);
  799. break;
  800. }
  801. }
  802. }
  803. //? Set custom themes directory from command line if provided
  804. if (cli.themes_dir.has_value()) {
  805. Theme::custom_theme_dir = cli.themes_dir.value();
  806. Logger::info("Using custom themes directory: " + Theme::custom_theme_dir.string());
  807. }
  808. //? Config init
  809. init_config(cli.low_color, cli.filter);
  810. //? Try to find and set a UTF-8 locale
  811. if (std::setlocale(LC_ALL, "") != nullptr and not std::string_view { std::setlocale(LC_ALL, "") }.contains(";")
  812. and str_to_upper(s_replace((string)std::setlocale(LC_ALL, ""), "-", "")).ends_with("UTF8")) {
  813. Logger::debug("Using locale " + std::locale().name());
  814. }
  815. else {
  816. string found;
  817. bool set_failure{};
  818. for (const auto loc_env : array{"LANG", "LC_ALL", "LC_CTYPE"}) {
  819. if (std::getenv(loc_env) != nullptr and str_to_upper(s_replace((string)std::getenv(loc_env), "-", "")).ends_with("UTF8")) {
  820. found = std::getenv(loc_env);
  821. if (std::setlocale(LC_ALL, found.c_str()) == nullptr) {
  822. set_failure = true;
  823. Logger::warning("Failed to set locale " + found + " continuing anyway.");
  824. }
  825. }
  826. }
  827. if (found.empty()) {
  828. if (setenv("LC_ALL", "", 1) == 0 and setenv("LANG", "", 1) == 0) {
  829. try {
  830. if (const auto loc = std::locale("").name(); not loc.empty() and loc != "*") {
  831. for (auto& l : ssplit(loc, ';')) {
  832. if (str_to_upper(s_replace(l, "-", "")).ends_with("UTF8")) {
  833. found = l.substr(l.find('=') + 1);
  834. if (std::setlocale(LC_ALL, found.c_str()) != nullptr) {
  835. break;
  836. }
  837. }
  838. }
  839. }
  840. }
  841. catch (...) { found.clear(); }
  842. }
  843. }
  844. //
  845. #ifdef __APPLE__
  846. if (found.empty()) {
  847. CFLocaleRef cflocale = CFLocaleCopyCurrent();
  848. CFStringRef id_value = (CFStringRef)CFLocaleGetValue(cflocale, kCFLocaleIdentifier);
  849. auto loc_id = CFStringGetCStringPtr(id_value, kCFStringEncodingUTF8);
  850. CFRelease(cflocale);
  851. std::string cur_locale = (loc_id != nullptr ? loc_id : "");
  852. if (cur_locale.empty()) {
  853. Logger::warning("No UTF-8 locale detected! Some symbols might not display correctly.");
  854. }
  855. else if (std::setlocale(LC_ALL, string(cur_locale + ".UTF-8").c_str()) != nullptr) {
  856. Logger::debug("Setting LC_ALL=" + cur_locale + ".UTF-8");
  857. }
  858. else if(std::setlocale(LC_ALL, "en_US.UTF-8") != nullptr) {
  859. Logger::debug("Setting LC_ALL=en_US.UTF-8");
  860. }
  861. else {
  862. Logger::warning("Failed to set macos locale, continuing anyway.");
  863. }
  864. }
  865. #else
  866. if (found.empty() and cli.force_utf) {
  867. Logger::warning("No UTF-8 locale detected! Forcing start with --force-utf argument.");
  868. } else if (found.empty()) {
  869. 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.";
  870. clean_quit(1);
  871. }
  872. #endif
  873. else if (not set_failure) {
  874. Logger::debug("Setting LC_ALL=" + found);
  875. }
  876. }
  877. //? Initialize terminal and set options
  878. if (not Term::init()) {
  879. Global::exit_error_msg = "No tty detected!\nbtop++ needs an interactive shell to run.";
  880. clean_quit(1);
  881. }
  882. if (Term::current_tty != "unknown") {
  883. Logger::info("Running on " + Term::current_tty);
  884. }
  885. configure_tty_mode(cli.force_tty);
  886. //? Check for valid terminal dimensions
  887. {
  888. int t_count = 0;
  889. while (Term::width <= 0 or Term::width > 10000 or Term::height <= 0 or Term::height > 10000) {
  890. sleep_ms(10);
  891. Term::refresh();
  892. if (++t_count == 100) {
  893. Global::exit_error_msg = "Failed to get size of terminal!";
  894. clean_quit(1);
  895. }
  896. }
  897. }
  898. //? Platform dependent init and error check
  899. try {
  900. Shared::init();
  901. }
  902. catch (const std::exception& e) {
  903. Global::exit_error_msg = "Exception in Shared::init() -> " + string{e.what()};
  904. clean_quit(1);
  905. }
  906. if (not Config::set_boxes(Config::getS("shown_boxes"))) {
  907. Config::set_boxes("cpu mem net proc");
  908. Config::set("shown_boxes", "cpu mem net proc"s);
  909. }
  910. //? Update list of available themes and generate the selected theme
  911. Theme::updateThemes();
  912. Theme::setTheme();
  913. //? Setup signal handlers for CTRL-C, CTRL-Z, resume and terminal resize
  914. std::atexit(_exit_handler);
  915. std::signal(SIGINT, _signal_handler);
  916. std::signal(SIGTSTP, _signal_handler);
  917. std::signal(SIGCONT, _signal_handler);
  918. std::signal(SIGWINCH, _signal_handler);
  919. std::signal(SIGUSR1, _signal_handler);
  920. std::signal(SIGUSR2, _signal_handler);
  921. // Add crash handlers to restore terminal on crash
  922. std::signal(SIGSEGV, _crash_handler);
  923. std::signal(SIGABRT, _crash_handler);
  924. std::signal(SIGTRAP, _crash_handler);
  925. std::signal(SIGBUS, _crash_handler);
  926. std::signal(SIGILL, _crash_handler);
  927. sigset_t mask;
  928. sigemptyset(&mask);
  929. sigaddset(&mask, SIGUSR1);
  930. pthread_sigmask(SIG_BLOCK, &mask, &Input::signal_mask);
  931. if (pthread_create(&Runner::runner_id, nullptr, &Runner::_runner, nullptr) != 0) {
  932. Global::exit_error_msg = "Failed to create _runner thread!";
  933. clean_quit(1);
  934. }
  935. else {
  936. Global::_runner_started = true;
  937. }
  938. //? Calculate sizes of all boxes
  939. Config::presetsValid(Config::getS("presets"));
  940. if (cli.preset.has_value()) {
  941. Config::current_preset = min(static_cast<std::int32_t>(cli.preset.value()), static_cast<std::int32_t>(Config::preset_list.size() - 1));
  942. Config::apply_preset(Config::preset_list.at(Config::current_preset));
  943. }
  944. {
  945. const auto [x, y] = Term::get_min_size(Config::getS("shown_boxes"));
  946. if (Term::height < y or Term::width < x) {
  947. pthread_sigmask(SIG_SETMASK, &Input::signal_mask, &mask);
  948. term_resize(true);
  949. pthread_sigmask(SIG_SETMASK, &mask, nullptr);
  950. Global::resized = false;
  951. }
  952. }
  953. Draw::calcSizes();
  954. //? Print out box outlines
  955. const bool term_sync = Config::getB("terminal_sync");
  956. cout << (term_sync ? Term::sync_start : "") << Cpu::box << Mem::box << Net::box << Proc::box << (term_sync ? Term::sync_end : "") << flush;
  957. //? ------------------------------------------------ MAIN LOOP ----------------------------------------------------
  958. if (cli.updates.has_value()) {
  959. Config::set("update_ms", static_cast<int>(cli.updates.value()));
  960. }
  961. uint64_t update_ms = Config::getI("update_ms");
  962. auto future_time = time_ms();
  963. try {
  964. while (not true not_eq not false) {
  965. //? Check for exceptions in secondary thread and exit with fail signal if true
  966. if (Global::thread_exception) {
  967. clean_quit(1);
  968. }
  969. else if (Global::should_quit) {
  970. clean_quit(0);
  971. }
  972. else if (Global::should_sleep) {
  973. Global::should_sleep = false;
  974. _sleep();
  975. }
  976. //? Hot reload config from CTRL + R or SIGUSR2
  977. else if (Global::reload_conf) {
  978. Global::reload_conf = false;
  979. if (Runner::active) Runner::stop();
  980. Config::unlock();
  981. init_config(cli.low_color, cli.filter);
  982. Theme::updateThemes();
  983. Theme::setTheme();
  984. Draw::banner_gen(0, 0, false, true);
  985. Global::resized = true;
  986. }
  987. //? Make sure terminal size hasn't changed (in case of SIGWINCH not working properly)
  988. term_resize(Global::resized);
  989. //? Trigger secondary thread to redraw if terminal has been resized
  990. if (Global::resized) {
  991. Draw::calcSizes();
  992. Draw::update_clock(true);
  993. Global::resized = false;
  994. if (Menu::active) Menu::process();
  995. else Runner::run("all", true, true);
  996. atomic_wait_for(Runner::active, true, 1000);
  997. }
  998. //? Update clock if needed
  999. if (Draw::update_clock() and not Menu::active) {
  1000. Runner::run("clock");
  1001. }
  1002. //? Start secondary collect & draw thread at the interval set by <update_ms> config value
  1003. if (time_ms() >= future_time and not Global::resized) {
  1004. Runner::run("all");
  1005. update_ms = Config::getI("update_ms");
  1006. future_time = time_ms() + update_ms;
  1007. }
  1008. //? Loop over input polling and input action processing
  1009. for (auto current_time = time_ms(); current_time < future_time; current_time = time_ms()) {
  1010. //? Check for external clock changes and for changes to the update timer
  1011. if (std::cmp_not_equal(update_ms, Config::getI("update_ms"))) {
  1012. update_ms = Config::getI("update_ms");
  1013. future_time = time_ms() + update_ms;
  1014. }
  1015. else if (future_time - current_time > update_ms) {
  1016. future_time = current_time;
  1017. }
  1018. //? Poll for input and process any input detected
  1019. else if (Input::poll(min((uint64_t)1000, future_time - current_time))) {
  1020. if (not Runner::active) Config::unlock();
  1021. if (Menu::active) Menu::process(Input::get());
  1022. else Input::process(Input::get());
  1023. }
  1024. //? Break the loop at 1000ms intervals or if input polling was interrupted
  1025. else break;
  1026. }
  1027. }
  1028. }
  1029. catch (const std::exception& e) {
  1030. Global::exit_error_msg = "Exception in main loop -> " + string{e.what()};
  1031. clean_quit(1);
  1032. }
  1033. return 0;
  1034. }